Tux
Tux

Reputation: 247

Move Files in Sequence in Bash

I'm trying to move folders from 1 to 05 and assigning numbers to them.

Example:

test-01 => test-221
test-02 => test-222
test-03 => test-223
test-04 => test-224
test-05 => test-225

I've tried this;

for num in $(seq -w $2 $3); {
  mv -v "test-$num" "test-$1$num"
}

using it like this;

./script.sh 2 21 25

but I'm getting output;

test-21 => test-221
test-22 => test-222
test-23 => test-223
test-24 => test-224
test-25 => test-225

which is of course wrong, see example :-)

I've also tried it this way;

for a in {1..5}; {
  for b in {21..25}; {
    echo "$a => $b"
  } #b
} #a

But I'm getting repeated output like this;

1 => 21
1 => 22
1 => 23
1 => 24
1 => 25
2 => 21
2 => 22
2 => 23
2 => 24
2 => 25
3 => 21
3 => 22
3 => 23
3 => 24
3 => 25
4 => 21
4 => 22
4 => 23
4 => 24
4 => 25
5 => 21
5 => 22
5 => 23
5 => 24
5 => 25

Upvotes: 1

Views: 445

Answers (6)

gniourf_gniourf
gniourf_gniourf

Reputation: 46893

From all the funny discussions we had in the comments (that was a really funny thread), I more or less (probably less) understood what you want... or rather, I made my own idea of what I believe you want. Let me rephrase what I understood (and please forgive me if this is not what you exactly requested).

You want a script that will take three non-negative numeric arguments X, Y and Z(with possibly leading 0's) with X<Y and you want to output test-M => test-N where:

  • M ranges from X to Y, left-padded with 0's so that the number of characters in M is the max number of characters in X and Y
  • N=M+Z, left-padded with 0's so that the number of characters in N is the max number of characters of X, Y, Z and Y+Z. E.g.,

    $ ./script 01 04 00220
    test-01 => test-00221
    test-02 => test-00222
    test-03 => test-00223
    test-04 => test-00224
    
    $ ./script 99 101 0
    test-099 => test-099
    test-100 => test-100
    test-101 => test-101
    
    $ ./script 99 101 00000
    test-099 => test-00099
    test-100 => test-00100
    test-101 => test-00101
    
    $ ./script 00 02 99
    test-00 => test-099
    test-01 => test-100
    test-02 => test-101
    

Also, you want a bash solution so that you can mv the corresponding files without needing to parse the output of another command.

Here we go, and hopefully you'll find some interesting stuff to dig too (remark, the output is of the form mv -nv xxx yyy rather than test-x => test-y; remove the echo when you're happy with this):

#!/bin/bash

prepend_source=test-
prepend_target=test-
append_source=
append_target=

shopt -s extglob

die() { printf >&2 "%s\n" "$@"; exit 1; }

is_number() { [[ $1 = +([[:digit:]]) ]]; }

is_in_range() { [[ -z ${1//0/} ]] || [[ ${1/#+(0)} = $((10#$1)) ]]; }

maxlength() {
    local u l=0 retvar=$1
    shift
    for i in "$@"; do
        u=${#i}
        ((u>l)) && ((l=u))
    done
    printf -v "$retvar" "%d" "$l"
}

X=$1
Y=$2
Z=$3

is_number "$X" || die "First argument is not a valid number"
is_number "$Y" || die "Second argument is not a valid number"
is_number "$Z" || die "Third argument is not a valid number"

(( 10#$X <= 10#$Y )) || die "Error: first argument is greater than second"

is_in_range "$X" || die "First argument out of range"
is_in_range "$Y" || die "Second argument out of range"
is_in_range "$Z" || die "Third argument out of range"
(( 10#$Y + 10#$Z >= 0 )) || die "Sum of second and last arguments is out of range"

maxlength "length_s" "$X" "$Y"
maxlength "length_t" "$X" "$Y" "$Z" "$((10#$Y+10#$Z))"

for ((i=10#$X;i<=10#$Y;++i)); do
    printf -v source "%s%.${length_s}d%s" "$prepend_source" "$i" "$append_source"
    printf -v target "%s%.${length_t}d%s" "$prepend_target" "$((10#$Z+$i))" "$append_target"
    # Here we're all done!
    echo mv -nv -- "$source" "$target" || die "Problem in mv" # or another error handle
done

I've added the variables prepend_source, append_source, prepend_target, append_target at the beginning of the script so that you can replace them easily by what you want. You could add option parsings to be able to set them from the command line (left as an exercise, unless you insist I do it for you).

Caveat. The numbers are directly handled by bash, so you must use them from within bash's arithmetic range which is (very likely) on a 64 bits machine: [-9223372036854775808,9223372036854775807]. So there's a lot you can do before reaching that. Now, don't worry, the script will not break if anything goes outside this range because I added explicit checks. If this is truly a limitation, you can always use bc or dc instead. The bc or dc implementation is left to the reader as an exercise. By the way, this only works with non-negative integers.


Is your solution as robust and general as this one?

Upvotes: 1

jkshah
jkshah

Reputation: 11713

Try using awk

#!/bin/bash

awk -v fa=$1 -v fb=$2 -v ta=$3  'BEGIN {  for(i=fa;i<=fb;i++) printf "test-%02d => test-%02d\n",i,i+ta  }'

Test

$bash -f main.sh 1 5 220
test-01 => test-221
test-02 => test-222
test-03 => test-223
test-04 => test-224
test-05 => test-225

Nice looking multi-line code on OP's request

#!/bin/bash

awk -v fa=$1 -v fb=$2 -v ta=$3  'BEGIN {  
  for(i=fa;i<=fb;i++) 
    printf "test-%02d => test-%02d\n",i,i+ta
}'

Now actual script for moving files

#!/bin/bash

awk -v fa=$1 -v fb=$2 -v ta=$3  'BEGIN {  
  for(i=fa;i<=fb;i++) {
    cmd = sprintf ("mv -v test-%02d test-%02d",i ,i+ta);
    # print cmd;
    system(cmd);
  }
}'

Upvotes: 2

Tux
Tux

Reputation: 247

Thanks to all your guys help this is what I have now;

for i in $(seq -w $1 $2); {
  echo "test-$i => test-$((10#$i+$3))"
} #for

and this is the output I get when I run ./script.sh 01 10 220 for example;

test-01 => test-221
test-02 => test-222
test-03 => test-223
test-04 => test-224
test-05 => test-225
test-06 => test-226
test-07 => test-227
test-08 => test-228
test-09 => test-229
test-10 => test-230

This was exactly what I wanted to achieve. Thanks a lot to "Robin Green" and "gniourf_gniourf".

and now moving of directories work correctly ^_^

mv -v "temp/test-$i" "temp/test-$((10#$i+$3))"

output of moved directories is;

‘temp/test-01’ -> ‘temp/test-221’
‘temp/test-02’ -> ‘temp/test-222’
‘temp/test-03’ -> ‘temp/test-223’
‘temp/test-04’ -> ‘temp/test-224’
‘temp/test-05’ -> ‘temp/test-225’
‘temp/test-06’ -> ‘temp/test-226’
‘temp/test-07’ -> ‘temp/test-227’
‘temp/test-08’ -> ‘temp/test-228’
‘temp/test-09’ -> ‘temp/test-229’
‘temp/test-10’ -> ‘temp/test-230’

Problem only occurs when I run; ./script.sh 01 10 0 this is the output I get;

test-01 => test-1
test-02 => test-2
test-03 => test-3
test-04 => test-4
test-05 => test-5
test-06 => test-6
test-07 => test-7
test-08 => test-8
test-09 => test-9
test-10 => test-10

as you can see no leading zeros =(

Upvotes: 0

Tux
Tux

Reputation: 247

Here is what I have so far;

for i in $(seq -w $1 $2); {
  echo "test-$i => test-$((i+$3))"
} #for

output I get by running ./script.sh 01 05 220

test-01 => test-221
test-02 => test-222
test-03 => test-223
test-04 => test-224
test-05 => test-225

so it looks like it matches my example :-)

Upvotes: 0

perreal
perreal

Reputation: 98108

from_a=1
to_a=5
width_a=2
from_b=220
width_b=3
for a in $(seq $from_a $to_a); do
    printf -v file_a "%0"$width_a"d" "$a"
    printf -v file_b "%0"$width_b"d" $(($a + $from_b))
    echo "test-$file_a => test-$file_b"
done

Upvotes: 1

Robin Green
Robin Green

Reputation: 33093

You only need one loop. But whichever way you count it, the number you use for one purpose is going to be the wrong number for the other purpose. So you need to use arithmetic to get the right number, e.g.:

$(( $num + 220 ))

Upvotes: 0

Related Questions