Reputation: 103
I have a command that seems to successfully replace double quotes (") with single quotes (') for filenames within the current directory. However, I need to do this recursively (i.e., all files within all subdirectories). Here is the code I am working with:
for f in *; do
if [[ -f "$f" ]]; then
new_name=$(echo "$f" | sed "s/\"/'/g")
mv "$f" "$new_name"
fi
done
Any advice would be greatly appreciated.
Upvotes: 4
Views: 643
Reputation: 295403
Best practice (and the much more efficient solution when you're dealing with a large and deeply-nested directory tree) is to use find
for this, not a for
loop in bash at all. Using Find goes into the tradeoffs between -print0
, -exec
and -execdir
; here, I'm using the last of these:
#!/usr/bin/env bash
# define the function we're going to export for access from a subprocess
do_replace() {
local name new_name old_quotes='"' new_quotes="'"
for name do # loops by default over "$@", the argument list given to the function
new_name=${name//$old_quotes/$new_quotes}
mv -- "$name" "$new_name"
done
}
export -f do_replace # actually export that function
# tell find to start a new copy of bash that can run the function we exported
# in each directory that contains one or more file or directory names with quotes.
# Using ``-execdir ... {} ';'`` to work around a MacOS bug
# ...use ``-execdir ... {} +`` instead with GNU find for better performance.
find . -depth -name '*"*' -execdir bash -c 'do_replace "$@"' _ {} ';'
That way there's a new copy of bash for each directory, so you aren't operating on names with /
s in them; this avoids some security holes that can happen if you're renaming files in directories a different user can write to.
That said, the easy thing (in bash 4.0 or later) is to enable globstar
, after which **
will recurse:
#!/usr/bin/env bash
# WARNING: This calculates the whole glob before it runs any renames; this can be very
# inefficient in a large directory tree.
case $BASH_VERSION in ''|[123].*) echo "ERROR: Bash 4.0+ required" >&2; exit 1;; esac
shopt -s globstar
for f in **; do
: "put your loop's content here as usual"
done
Upvotes: 4