简体   繁体   中英

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.

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

Using , with full regex capabilities to handle the new case of the parent directory baselines :

perl -MFile::Find -MFile::Copy -we '
    find({
        no_chdir => 1,
        wanted => sub {
             if (-f && m/README\.md$/) {
                 my $mod = my $file = $File::Find::name;
                 $mod =~ s@.*([^/]+)/.*@output/${1}.md@;
                 print "$file => $mod\n";
                 #copy("$file", "$mod") or die $!;
             }
         }
     }, @ARGV);
' ./baselines/

This is in debug mode, to do it for real, remove the print command and uncomment the copy() line.

What about this

$ find baselines -type f -name "README.md" -exec echo output/$(dirname {}|sed 's@/@_@/g').md \;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM