I have a main directory, let's say main. This main directory has some folders, among which are three folders named ccc ddd and lll, which are the only folders I want to target and work on. Each of these subfolders has some folders inside which are all named the same in the three subfolders, so that ccc ddd lll subfolders contain folders of the same name.

Then, each of these subsubfolders that are inside ccc ddd lll has a number of files named with a certain name like this c_000 d_000 l_000, c_001 d_001 l_001 and so on.

What I want to do first is rename these files so that the directory of their two parents will be attached in the beginning of the file name like this ccc_foo1_c_000 ddd_foo1_d_000 lll_foo1_l_000 and so on. I'm asking if anyone can advise me how this can be done in a .sh script?

This is a simple tree structure of my main folder:

`-- main
    |-- ccc
    |   `-- foO1
    |   |    |-- c_000
    |   |    `-- c_001
    |   |
         -- foo2
    |       |-- c_000
    |       `-- c_001
    |-- ddd
    |   `-- foO1
    |   |    |-- d_000
    |   |    `-- d_001
    |   |
         -- foo2
    |       |-- d_000
    |       `-- d_001
    |-- lll
    |      `-- foO1
    |  |    |-- l_000
    |  |    `-- l_001
    |  |
    |   -- foo2
    |      |-- l_000
    |      `-- l_001
    |-- aaa
    |-- bbb

3 Answers 3


You could do something like this:

for dir in ccc ddd lll; do 
    find "main/$dir" -type f -print0 | 
        while IFS= read -r -d '' f; do 
            dd=$(dirname "$f")
            mv "$f" "$dd"/"$new"

After the above script, your files will look like this:

`-- main
    |-- ccc
    |   |-- fo01
    |   |   |-- ccc_fo01_c_000
    |   |   `-- ccc_fo01_c_001
    |   `-- fo02
    |       |-- ccc_fo02_c_000
    |       `-- ccc_fo02_c_001
    |-- ddd
    |   |-- fo01
    |   |   |-- ddd_fo01_d_000
    |   |   `-- ddd_fo01_d_001
    |   `-- fo02
    |       |-- ddd_fo02_d_000
    |       `-- ddd_fo02_d_001
    `-- lll
        |-- fo01
        |   |-- lll_fo01_l_000
        |   `-- lll_fo01_l_001
        `-- fo02
            |-- lll_fo02_l_000
            `-- lll_fo02_l_001


  • for dir in ccc ddd lll; do ...; done : do this only for these three directory names, saving each of them as $dir.
  • find "main/$dir" -type f -print0 | : find all files (type f) in main/$dir, and print them separated by the null string (-print0) to ensure that it works even if your file/directory names contain newlines.
  • while IFS= read -r -d '' f; do : this will iterate over the results of find, saving each as $f. The IFS= is needed to avoid breaking on whitespace, the -r is so that backslashes are not treated specially and the -d '' allows the reading of null-delimited input.
  • dd=$(dirname "$f") : $dd is now the directory where the current file resides. For example, main/ccc/fo01.
  • new="${f/main\/}" : this is using the shell's string manipulation abilities to remove the string main/ from the file's path.
  • new="${new//\//_}" : as above only now we are replacing all / with underscores. This results in a string like ccc_fo01_c000.
  • mv "$f" "$dd"/"$new" : finally, we rename the original file $f to $dd/$new. Remember that $dd is the directory this file was found in and $new is now the name you want to rename it to.

I suggest you add an echo before the mv to test before actually doing this.

  • Thank you very much for the answer! Did you have the chance to check the second part of the question? regarding copying depending on the names of the files manually copied.
    – Tak
    Commented Mar 12, 2015 at 16:11
  • 1
    @shepherd please post that as a separate question.
    – terdon
    Commented Mar 12, 2015 at 17:15
  • no worries. Done :) unix.stackexchange.com/questions/189833/…
    – Tak
    Commented Mar 12, 2015 at 23:03
  • find is overly complex here, given that the directory structure has fixed depth. Even keeping find involved, you could simplify by calling find ccc ddd lll … instead of first looping over the toplevel directories. Commented Mar 13, 2015 at 0:35

Here's a simple POSIX shell solution that loops over the files, parses the directory parts of the names and renames them one by one.

for x in ccc/*/* ddd/*/* lll/*/*; do
  dir2=${x%/*}; dir1=${dir2%/*}; dir2=${dir2#"$dir1"}
  mv -- "$x" "${x%/*}/${dir1}_${dir2}_${x##*/}"

In bash, you can use the parameter expansion string replacement feature to avoid having to parse the file names.

for x in ccc/*/* ddd/*/* lll/*/*; do
  mv -- "$x" "${x%/*}/${x//\//_}"

In zsh, you can use zmv to rename files based on patterns.

autoload -U zmv
zmv '(ccc|ddd|lll)/(*)/(*)' '$1/$2/${1}_${2}_$3'

For a somewhat more soft-coded solution run from within your directory main:


for f in $(find . -name '*_00*'); do
    mv "$f" "$(dirname "$f")/$(dirname "${f:2}" | tr "/" "_")_$(basename $f)"

This should iterate through all directories recursively looking for all files containing the pattern *_00*. Each of this files is renamed separately with a combination consisting of the path ($(dirname $"f")), the transformed path containing underscores and the first two characters removed ($(dirname "${f:2}" | tr "/" "_")) and the original filename itself ($(basename $f)).

  • (1) When I see “c_000, c_001, and so on”, I assume that “and so on” can continue to c_009, c_010, ..., c_042, ..., c_099, c_100, ....  Your *_00* pattern won’t match those names (beyond c_009).  (And consider using [cdl]_....)  (2) This answer will process the aaa and bbb directories (i.e., it’s not restricted to ccc, ddd and lll).  (3) Good job, putting $f in quotes three times.  But why do you leave it unquoted the fourth time? (4) See Why is looping over find’s output bad practice? Commented Feb 1 at 17:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .