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.
CodePudding user response:
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[@]}"
CodePudding user response:
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.
