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

Time:11-09

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.

CodePudding user response:

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" {}  

CodePudding user response:

The argument to rename should be the file itself, not */${old}. You also have a number of quoting errors, and a useless 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.)

  • Related