com zsh
:
autoload zmv
zmv '(**/)(*.*)' '$1${2//./_}'
Caso contrário, se você tiver acesso a bash
(embora ksh93
ou zsh
também funcione), você sempre poderá fazer:
find . -depth ! -name '.*' -name '*.*' -exec bash -c '
for file do
dir=${file%/*}
base=${file##*/}
mv -i -- "$file" "$dir/${base//./_}"
done' sh {} +
(embora você perca as verificações de integridade feitas por zmv
).
Aqueles (intencionalmente) não renomear arquivos ocultos.
O ponto é processar a lista em profundidade primeiro (que zmv
faz por padrão e usando -depth
em find
) e apenas renomear o nome base do arquivo.
Para que você faça:
mv -- a.b/c.d a.b/c_d
e então :
mv -- a.b a_b
Observe também que, para a solução não-zmv, se você tiver um arquivo a.b
e a_b
no mesmo diretório, o mv -- a.b a_b
ficará feliz em mover a.b
para %código%. Para evitar isso, se estiver em um sistema GNU, você pode adicionar a opção a_b/
a -T
.
Como eu vejo, você tentou editar a resposta para usar um mv
loop.
Observe que você geralmente deve evitar while read
loops, mesmo neste caso em que um comando por linha deve ser executado de qualquer maneira. Se você realmente quer usar um, então fazer certo é complicado e envolve ksh / bash / zshisms:
{
find . -depth ! -name '.*' -name '*.*' -exec printf '%sautoload zmv
zmv '(**/)(*.*)' '$1${2//./_}'
' {} + |
while IFS= read -rd '' file; do
dir=${file%/*}
base=${file##*/}
mv -i -- "$file" "$dir/${base//./_}"
done <&3 3<&-
} 3<&0
(você pode substituir while read
por -exec printf '%s
se sua pesquisa for compatível). -print0
' {} +
Você precisa:
-
-print0
/-exec printf '%s
, já que você não pode confiar em nova linha como o separador, já que nova linha é um caractere válido em um nome de arquivo-d ''
' {} + - como resultado, você precisa de
read
(IFS=
lê até NUL em vez de NL) -
IFS
(isto é, remover todos os caracteres de espaço em branco deread
para-r
, caso contrário, retira-os do final do registro de entrada). -
read
senãomv
trata a barra invertida como um caractere de escape (para o campo e separador de registro). -
essa pequena dança com descritores de arquivo porque você quer que a entrada padrão de
-i
seja o terminal (para as respostas aos promptsfind
), não o pipe de %code% .