MasterScrat
MasterScrat

Reputation: 7366

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.

Upvotes: 0

Views: 368

Answers (3)

Gilles Quénot
Gilles Quénot

Reputation: 185126

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.

Upvotes: 0

Gilles Quénot
Gilles Quénot

Reputation: 185126

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

Upvotes: 2

Diego Torres Milano
Diego Torres Milano

Reputation: 69208

What about this

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

Upvotes: -1

Related Questions