With zsh
:
autoload zmv
zmv '(**/)(*.*)' '$1${2//./_}'
Otherwise, if you have access to bash
(though ksh93
or zsh
will do as well), you could always do:
find . -depth ! -name '.*' -name '*.*' -exec bash -c '
for file do
dir=${file%/*}
base=${file##*/}
mv -i -- "$file" "$dir/${base//./_}"
done' sh {} +
(though you'll miss the sanity checks done by zmv
).
Those (intentionally) don't rename hidden files.
The point is to process the list depth-first (which zmv
does by default, and using -depth
in find
) and only rename the basename of the file.
So that you do:
mv -- a.b/c.d a.b/c_d
and then:
mv -- a.b a_b
Also note that for the non-zmv solution, if you have a a.b
file and a a_b
directory in the same directory, then mv -- a.b a_b
will happily move a.b
into a_b/
. To avoid that, if on a GNU system, you can add the -T
option to mv
.
As I see you tried to edit the answer to use a while read
loop.
Note that you should generally avoid while read
loops, even in this case where one command per line has to be run anyway. If you really want to use one, then doing it right is tricky and involves ksh/bash/zshisms:
{
find . -depth ! -name '.*' -name '*.*' -exec printf '%s\0' {} + |
while IFS= read -rd '' file; do
dir=${file%/*}
base=${file##*/}
mv -i -- "$file" "$dir/${base//./_}"
done <&3 3<&-
} 3<&0
(you can replace -exec printf '%s\0' {} +
with -print0
if your find supports it).
You need:
-print0
/-exec printf '%s\0' {} +
as you can't rely on newline as the separator since newline is a valid character in a filename
- as a result, you need
-d ''
(read
reads until NUL instead of NL)
IFS=
(that is remove all the whitespace characters from IFS
for read
as otherwise strips them from the end of the input record).
-r
as otherwise read
treats backslash as an escape character (for the field and record separator).
- that litte dance with file descriptors because you want the standard input of
mv
to be the terminal (for the answers to the -i
prompts), not the pipe from find
.
mv -iv
will ask interactively if it should overwrite. But my try isn't good anyway, there is already a great answer below