Search code examples
bashcp

Renaming files using their folder's name with bash `find`


My goal is to find all files named README.md in sub-folders, and to copy them in an output folder, using the name of their original folder as their new name.

Ok that sounds complicated. Let's see a concrete example:

baselines
├── combined_tree_local_conflict_obs
│   ├── README.md
│   └── sparse_small_apex_maxdepth2_spmaxdepth30.yaml
└── global_density_obs
    ├── README.md
    └── sparse_small_apex_expdecay_maxt1000.yaml

I want to copy the README.md files to the output folder with the names combined_tree_local_conflict_obs.md and global_density_obs.md.

So in the end I'd have:

baselines
├── output
│   ├── combined_tree_local_conflict_obs.md
│   └── global_density_obs.md
├── combined_tree_local_conflict_obs
│   ├── README.md
│   └── sparse_small_apex_maxdepth2_spmaxdepth30.yaml
└── global_density_obs
    ├── README.md
    └── sparse_small_apex_expdecay_maxt1000.yaml

I don't understand why my bash command doesn't work:

$ find baselines -type f -name "README.md" -exec echo output/$(basename $(dirname {})).md \;
output/..md
output/..md

(I'm not copying the files yet but just printing their new path to debug the command.)

The find command does work:

$ find baselines -type f -name "README.md" -exec echo {} \;
baselines/combined_tree_local_conflict_obs/README.md
baselines/global_density_obs/README.md

Extracting the folder name does work:

$ echo $(basename $(dirname "baselines/combined_tree_local_conflict_obs/README.md"))
combined_tree_local_conflict_obs

But! somehow when I put them together it doesn't work.

I'm not so much interested in how to solve this problem in another way, but rather to understand why my command doesn't work.


Solution

  • Like Charles Duffy explain in comments,

    $(basename $(dirname {})) runs before find even starts. It can't possibly operate on the names of the specific files that were found.

    I have some better solutions...

    First:

    cd baselines
    

    Then, like this to re-use your code:

    find . -type f -name "README.md" -exec bash -c '
        echo cp "$1" "./output/$(dirname "$1").md"
    ' -- {} \;
    

    or

    find . -type f -name "README.md" -exec bash -c '
        for file; do
            echo cp "$file" "./output/$(dirname "$file").md"
        done
    ' -- {} +
    

    Check https://mywiki.wooledge.org/UsingFind

    or

    for file in */README.md; do
        echo cp "$file" "./output/$(dirname "$file").md"
    done
    

    Drop the echo command when the output looks good for you (don't try to feed bash STDIN).


    cp ./combined_tree_local_conflict_obs/README.md ./output/./combined_tree_local_conflict_obs.md
    cp ./global_density_obs/README.md ./output/./global_density_obs.md