简体   繁体   中英

Issues renaming files using bash script with input from .txt file with find -exec rename command

Update 01/12/2022

With triplee's helpful suggestions, I resolved it to take both files & directories by adding a comma in between f and d, the final code now looks like this:

while read -r old new; 
   do echo "replacing ${old} by ${new}" >&2 
   find '/path/to/dir' -depth -type d,f -name "$old" -exec rename 
   "s/${old}/${new}/" {} ';' 
done <input.txt

Thank you!


Original request:

I am trying to rename a list of files (from $old to $new ), all present in $homedir or in subdirectories in $homedir .

In the command line this line works to rename files in the subfolders: find ${homedir}/ -name ${old} -exec rename "s/${old}/${new}/" */${old} ';'

However, when I want to implement this line in a simple bash script getting the $old and $new filenames from input.txt , it doesn't work anymore...

input.txt looks like this:

name_old name_new
name_old2 name_new2
etc...

the script looks like this:

#!/bin/bash
homedir='/path/to/dir'

cat input.txt | while read old new; 
do
    echo 'replacing' ${old} 'by' ${new}
    find ${homedir}/ -name ${old} -exec rename "s/${old}/${new}/" */${old} ';'
done

After running the script, the text line from echo with $old and $new filenames being replaced is printed for the entire loop, but no files are renamed. No error is printed either. What am I missing? Your help would be greatly appreaciated!

I checked whether the $old and $new variables were correctly passed to the find -exec rename command, but because they are printed by echo that doesn't seem to be the issue.

If you add an echo , like -exec echo rename... , you'll see what actually gets executed. I'd say that both the path to $old is wrong (you're not using the result of find in the -exec clause), and */$old isn't quoted and might be expanded by the shell before find ever gets to see it.

You're also having most other expansions unquoted, which can lead to all sorts of trouble.

You could do it in pure Bash (drop echo when output looks good):

shopt -s globstar
for f in **/"$old"; do echo mv "$f" "${f/%*/$new}"; done

Or with rename directly, though this would run into trouble if too many files match (drop -n when output looks good):

rename -n "s/$old\$/$new/" **/"$old"

Or with GNU find, using -execdir to run in the same directory as the matching file (drop echo when output looks good):

find -type f -name "$old" -execdir echo mv "$old" "$new" \;

And finally, a version with find that spawns just a single subshell (drop echo when output looks right):

find -type f -name "$old" -exec bash -c '
    new=$1
    shift
    for f; do
        echo mv "$f" "${f/%*/$new}"
    done
' bash "$new" {} +

The argument to rename should be the file itself, not */${old} . You also have a number of quoting errors, and auseless cat ).

#!/bin/bash

while read -r old new; 
do
    echo "replacing ${old} by ${new}" >&2
    find /path/to/dir -name "$old" -exec rename "s/${old}/${new}/" {} ';'
done <input.txt

Running find multiple times on the same directory is hugely inefficient, though. Probably a better solution is to find all files in one go, and abort if it's not one of the files on the list.

find /path/to/dir -type f -exec sh -c '
    for f in "$@"; do
       awk -v f="$f" "f==\$1 { print \"s/\" \$1 \"/\" \$2 \"/\" }" "$0" |
       xargs -I _ -r rename _ "$f"
    done' input.txt {} +

(Untested; probably try with echo before you run this live.)

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