5

I have a directory containing subdirectories containing many files named as:

file1.tif
file2.tif
file3.tif
file4.tif
file5.tif
file6.tif
file7.tif...

I want to add a "cycling" suffix c1, c2 and c3, to get the file names:

file1c1.tif
file2c2.tif
file3c3.tif
file4c1.tif
file5c2.tif
file6c3.tif
file7c1.tif...

And this in all subdirectories of the top directory.

But I cannot find out how to do that.

Is that possible? How?

7

With find + the perl based prename (aka rename on some earlier Ubuntus) you could match the sequence of digits before a literal . and then write it in the desired format using modulo division e.g.

find . -name '*.tif' -execdir prename -vn -- 's/(\d+)\./sprintf "%dc%d.", $1, ($1-1)%3+1/e' {} +

(the -vn flags alllow you to do a 'dry run' to see how the files would be renamed before committing).


Ex. given

$ tree
.
├── bar
│   ├── file1.tif
│   ├── file2.tif
│   ├── file3.tif
│   ├── file4.tif
│   ├── file5.tif
│   ├── file6.tif
│   └── file7.tif
├── foo
│   ├── egg
│   │   ├── file1.tif
│   │   └── file2.tif
│   ├── file1.tif
│   ├── file2.tif
│   └── file3.tif
└── spam
    ├── file1.tif
    └── file2.tif

4 directories, 14 files

Then

find . -name '*.tif' -execdir prename -- 's/(\d+)\./sprintf "%dc%d.", $1, ($1-1)%3+1/e' {} +

results in

$ tree
.
├── bar
│   ├── file1c1.tif
│   ├── file2c2.tif
│   ├── file3c3.tif
│   ├── file4c1.tif
│   ├── file5c2.tif
│   ├── file6c3.tif
│   └── file7c1.tif
├── foo
│   ├── egg
│   │   ├── file1c1.tif
│   │   └── file2c2.tif
│   ├── file1c1.tif
│   ├── file2c2.tif
│   └── file3c3.tif
└── spam
    ├── file1c1.tif
    └── file2c2.tif

4 directories, 14 files
6

Edit based on comment:

If you want to cycle the counter i.e. <number> in c<number> to cycle between 1 to 3:

Dry-run:

shopt -s globstar
for d in **/*/; do 
    ( cd "$d" && pwd && count=1 && shopt -s nullglob && for f in file*.tif; do 
        echo mv -i "$f" "${f%.tif}"c${count}.tif
        if ((count=3)); then 
            count=1
        else
            ((count++))
        fi
     done ) 
done

Action:

shopt -s globstar
for d in **/*/; do 
    ( cd "$d" && count=1 && shopt -s nullglob && for f in file*.tif; do 
        mv -i "$f" "${f%.tif}"c${count}.tif
        if ((count=3)); then 
            count=1
        else
            ((count++))
        fi
     done ) 
done

Example:

$ tree
.
├── bar
│   ├── file1.tif
│   ├── file2.tif
│   ├── file3.tif
│   └── file4.tif
├── foo
│   ├── egg
│   │   ├── file1.tif
│   │   └── file2.tif
│   ├── file1.tif
│   ├── file2.tif
│   └── file3.tif
└── spam
    ├── file1.tif
    └── file2.tif

$ shopt -s globstar
$ for d in **/*/; do ( cd "$d" && pwd && count=1 && shopt -s nullglob && for f in file*.tif; do \
    echo mv -i "$f" "${f%.tif}"c${count}.tif; if ((count=3)); then count=1; else ((count++)); fi; done ); done
/home/foobar/bar
mv -i file1.tif file1c1.tif
mv -i file2.tif file2c2.tif
mv -i file3.tif file3c3.tif
mv -i file4.tif file4c1.tif
/home/foobar/foo
mv -i file1.tif file1c1.tif
mv -i file2.tif file2c2.tif
mv -i file3.tif file3c3.tif
/home/foobar/foo/egg
mv -i file1.tif file1c1.tif
mv -i file2.tif file2c2.tif
/home/foobar/spam
mv -i file1.tif file1c1.tif
mv -i file2.tif file2c2.tif

$ for d in **/*/; do ( cd "$d" && count=1 && shopt -s nullglob && for f in file*.tif; do \
    mv -i "$f" "${f%.tif}"c${count}.tif; if ((count=3)); then count=1; else ((count++)); fi; done ); done

$ tree
.
├── bar
│   ├── file1c1.tif
│   ├── file2c2.tif
│   ├── file3c3.tif
│   └── file4c1.tif
├── foo
│   ├── egg
│   │   ├── file1c1.tif
│   │   └── file2c2.tif
│   ├── file1c1.tif
│   ├── file2c2.tif
│   └── file3c3.tif
└── spam
    ├── file1c1.tif
    └── file2c2.tif

Original Answer:

With bash:

shopt -s globstar
for d in **/*/; do ( cd "$d" && pwd && count=1 && shopt -s nullglob \
    && for f in file*.tif; do echo mv -i "$f" "${f%.tif}"c${count}.tif; \
        ((count++)); done ); done
  • Here we are looping over the directory names, globstar shell option lets us to traverse directory recursively

  • The part inside () runs in a subshell, this is to prevent the cd being modified in the current shell

  • We are cd-ing into the directories, finding the file*.tif files and renaming them as needed

  • keeping an incrementing counter, count, for each file while renaming


The above is dry-run, and will show you the directory where the renaming will be done and what mv command will be run. If satisfied, do:

shopt -s globstar
for d in **/*/; do ( cd "$d" && count=1 && shopt -s nullglob && \
  for f in file*.tif; do mv -i "$f" "${f%.tif}"c${count}.tif; ((count++)); done ); done

There is a caveat, that as shell does lexical sorting so files e.g. file10.tif will be renamed earlier than file2.tif. If you don't want that, do some sorting before renaming or use zsh with numericglobsort option.

  • Thanks a lot, heemayl! It would have taken me weeks to write that... There is still something not right, though: the c1, c2... do not "cycle": I get (as well as you in your example "bar" directory): file1c1, file2c2, file3c3, file4c4, file5c5... not: file1c1, file2c2, file3c3, file4c1, file5c2... – Philippe P Aug 30 '16 at 14:38
  • @PhilippeP Meaning after 3 files you want to set the counter to 1 again? – heemayl Aug 30 '16 at 14:40
  • @PhilippeP Check my edits.. – heemayl Aug 30 '16 at 15:01
  • Yes, great and thanks! That's the loop I needed! One last question: when I run your script from the master folder of interest, I do get (not always, and I don't understand why...) this message: mv: cannot stat 'file*.tif': No such file or directory Your dry-run shows that the directories are correctly found: .../hypoxia-individual/A1 mv -i file*.tif file*c1.tif .../hypoxia-individual/A2 ...and so on... What do I do wrong? – Philippe P Aug 31 '16 at 9:56
  • @PhilippeP Ahhh, forgot about that, thats when no match is found, the glob pattern would be treated literally then, you need nullglob..fixed. – heemayl Aug 31 '16 at 10:51
0

Thanks to heemayl, everything got sorted out. We had to modify the indicated cycle-increment loop a bit, since it did not work in our hands. We changed: if ((count=3)) into: if (("$count"==3)) The final code we are now successfully using is:

    shopt -s globstar
for d in **/*/; do 
    ( cd "$d" && count=1 && shopt -s nullglob && for f in *.tif; do 
       echo mv -i "$f" "${f%.tif}"c${count}.tif

if (("$count"==3)); then 
count=1
        else 
((count++))
        fi
     done ) 
done

Thanks again to heemayl for his/her help and patience...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.