The easy way to get slhck’s script
to search subfolders and files recursively, if you're using bash,
is to add shopt -s globstar
and then change *
to **
.
This will not work correctly
if you have directories with “illegal” characters in their names.
You can work around this simply by running the script n+1 times
where n is the maximum number of directories with illegal characters
in any path.
For example, if you have a f@cat
directory,
and a dog#
directory below that, and a fox!
directory below that,
you would need to run the script four times.
I give a better way of doing that, below.
- slhck’s script really should say
mv -i
instead of mv
.
If you had files named, say, cost+tax
and cost-tax
,
the script will rename them both to costtax
(or cost_tax
, after we make that change).
This will clobber the first file with the second file.
The -i
(interactive) option will cause mv
to ask for confirmation.
You’ll have to handle such collisions like that manually.
- To replace “illegal” characters with underscores,
change
${file//[^0-9A-Za-z.]}
to ${file//[^0-9A-Za-z.]/_}
(or, better yet, ${file//[^0-9A-Za-z._]/_}
).
So, with the above changes, slhck’s script becomes
shopt -s globstar
for f in "$1"/**
do
dir="$(dirname "$f")"
file="$(basename "$f")"
mv -i -- "$f" "${dir}/${file//[^0-9A-Za-z._]/_}"
done
When running this,
be absolutely sure to provide an argument (e.g., .
);
otherwise it will try to rename all files in the file system
(starting at /
).
You should probably put something in the script
to verify that the $1
argument is not null.
This has problems with directories with illegal characters in their names
because the **
expands to a list of all the files and directories
in the sub tree under $1
with branches in top-to-bottom order.
So, if you have f@cat/dog#
,
it will see f@cat
and f@cat/dog#
as arguments.
So it will rename f@cat
to f_cat
, and then look for f@cat/dog#
— which no longer exists, because it has been renamed to f_cat/dog#
.
We can fix that by doing
find "$1" -depth -name '*[^0-9A-Za-z._]*' -exec sh -c \
'for f do dir="$(dirname "$f")"; file="$(basename "$f")";
mv -i -- "$f" "${dir}/${file//[^0-9A-Za-z.]/_}"; done' sh {} +
The -depth
option to find
tells it
to look at directory branches in bottom-to-top order.
The -name
directive causes it to look only at file names
that need to be renamed.
(The other script will give error messages
when it tries to rename files to themselves,
because their names don’t have any illegal characters.)
find
then passes the names to a shell
that does the same thing as the script, but as a single command.