0

In Bash, I wish to rename many files based upon a predefined dict with many replacement strings. I have multiple files nested in a directory tree, like:

./aa
./b/aa
./b/bb
./c/aa
./c/d/ee

I have a "sed script" dict.sed whose contents is like:

s|aa|xx|g
s|ee|yy|g

Can I recursively find and rename files matching aa and ee to xx and yy, respectively, and preserving the directory structure, using said sed script?

At the moment I have:

function rename_from_sed() {
    IFS='|' read -ra SEDCMD <<< "$1"
    find "." -type f -name "${SEDCMD[1]}" -execdir mv {} "${SEDCMD[2]}" ';'
}

while IFS='' read -r line || [[ -n "${line}" ]]; do
    rename_from_sed "$line"
done < "dict.sed"

This seems to work, but is there a better way, perhaps using sed -f dict.sed instead of parsing dict.sed line-by-line? The current approach means I need to specify the delimiter, and is also limited to exact filenames.

The closest other answer is probably https://stackoverflow.com/a/11709995/3329384 but that also has the limitation that directories may also be renamed.

Nick P
  • 381
  • 5
  • 7

2 Answers2

0

That seems odd: iterating over the lines in a sed script. You want:

Can I recursively find and rename files

So iterate over files, not over the sed script.

find .... | while IFS= read -r file; do
     newfile=$(sed -f dict.sed <<<"$file")
     mv "$file" "$newfile"
done

Related https://mywiki.wooledge.org/BashFAQ/001 and https://mywiki.wooledge.org/BashFAQ/020

but is there a better way

Sure - do not use Bash and do use Sed. Write the whole script in Python or Perl - it will be 1000 times faster to use a tool which will do all the operations in a single thread without any fork() calls.


Anyway, you can also parse sed script line by line and run a single find call.

find "." -type f '(' -name aa -execdir mv {} xx ';' ')' -o '(' -name ee -execdir mv {} yy ';' ')'

You would have to build such call, like:

findargs=()
while IFS='|' read -r _ f t _; do
    if ((${#findargs[@]})); then findargs+=('-o'); fi
    findargs+=( '(' -name "$f" -execdir {} "$t" ';' ')' )
done < "dict.sed"
find . -type f "${findargs[@]}"
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
0

Assuming lines in the file dict is in the form of from|to, below is an implementation in pure bash, without using find and sed.

#!/bin/bash

shopt -s globstar nullglob

while IFS='|' read -r from to; do
    for path in ./**/*"$from"*; do
        [[ -f $path ]] || continue
        basename=${path##*/}
        echo mv "$path" "${path%/*}/${basename//"$from"/"$to"}"
    done
done < dict

--

$ cat dict
aa|xx
ee|yy

Drop the echo if you are satisfied with the output.

M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17