0

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.

Eva
  • 1
  • 1
  • Could it be that `old` contains a space? Also, the unquoted `*/${old}` means that the wildcard is evaluated before `find` is invoked. Aside from this, I would for debugging prefix the `rename` by an echo, to see what commands would be executed. Also it could be a good idea to do a `set -x` in your script. – user1934428 Nov 08 '22 at 15:34
  • BTW, wouldn't it be easier to use instead of the `-exec` part a `-execdir mv "$old" "$new" \:`? – user1934428 Nov 08 '22 at 15:37
  • I took the liberty to remove the tag _exec_, because the question is not related to the `exec` command. – user1934428 Nov 08 '22 at 15:38

2 Answers2

0

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" {} +
Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
0

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.)

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • Thanks! The bottom (efficient) version gives an error about -exec supporting only one instance of {} The top one works for the files but not the directories I have in the list. Is it possible to make this work for directories too in the same command? As it's not specified (-d / -f) now, I hoped it would take all types. – Eva Nov 15 '22 at 15:44
  • Ah, I see, I updated with hopefully a sensible fix for the second one. Sorry for the bug. To also include directories, try `\( -type f -o -type d \)` but then you probably also want `-d` for a depth-first traversal. – tripleee Nov 15 '22 at 15:47