简体   繁体   中英

Recursively Rename Files and Directories with Bash on macOS

I'm writing a script that will perform some actions, and one of those actions is to find all occurrences of a string in both file names and directory names, and replace it with another string.

I have this so far

find . -name "*foo*" -type f -depth | while read file; do
    newpath=${file//foo/bar}
    mv "$file" "$newpath"
done

This works fine as long as the path to the file doesn't also contain foo , but that isn't guaranteed.

I feel like the way to approach this is to ONLY change the file names first, then go back through and change the directory names, but even then, if you have a structure that has more than one directory with foo in it, it will not work properly.

Is there a way to do this with built in macOS tools? (I say built-in, because this script is going to be distributed to some other folks in our organization and it can't rely on any packages to be installed).

Separating the path_name from the file_name, something like.

#!/usr/bin/env bash

while read -r file; do
  path_name="${file%/*}"; printf 'Path is %s\n' "$path_name"
  file_name="${file#"$path_name"}"; printf 'Filename is %s\n' "$file_name"
  newpath="$path_name${file_name//foo/bar}"
  echo mv -v "$file" "$newpath"
done < <(find . -name "*foo*" -type f)

Have a look at basename and dirname as well.

The printf 's is just there to show which is the path and the filename.

The script just replace foo to bar from the file_name, It can be done with the path_name as well, just use the same syntax.

newpath="${path_name//bar/more}${file_name//foo/bar}"

So renaming both path_name and file_name.

Or renaming the path_name and then the file_name like your idea is an option also.

path_name="${file%/*}"
file_name="${file#"$path_name"}"
new_pathname="${path_name//bar/more}"

mv -v "$path_name" "$new_pathname"

new_filename="${file_name//foo/bar}"

mv -v "${new_pathname%/*}$file_name" "$new_pathname$new_filename"

There are no additional external tool/utility used, except from the ones being used by your script.

Remove the echo If you're satisfied with the result/output.

You can use -execdir to run a command on just the filename (basename) in the relevant directory:

find . -depth -name '*foo*' -execdir bash -c 'mv -- "${1}" "${1//foo/bar}"' _ {} \;

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