Minha abordagem:
find . -depth -name "* *" -execdir bash -c 'pwd; for f in "$@"; do mv -nv "$f" "${f// /_}"; done' dummy {} +
Versão multilinha para legibilidade:
find . -depth -name "* *" -execdir \
bash -c '
pwd
for f in "$@"; do
mv -nv "$f" "${f// /_}"
done
' dummy {} +
Explicação:
-
find . -name "* *"
encontra objetos que precisam ser renomeados. Nota find
é muito flexível com seus testes, portanto, se você quiser (por exemplo) renomear apenas os diretórios, comece com find . -depth -type d -name "* *"
.
-
-execdir
executa o processo fornecido ( bash
) em um diretório no qual o objeto está, portanto, qualquer caminho passado por {}
é sempre como ./bar
, não ./foo/bar
. Isso significa que não precisamos nos preocupar com todo o caminho. A desvantagem é mv -v
não mostrar o caminho, então eu adicionei pwd
apenas para informações (você pode omiti-lo se quiser).
-
bash
nos permite usar a sintaxe "${baz// /_}"
.
-
-depth
garante que o seguinte não acontecerá: find
renomeia um diretório (se aplicável) e, em seguida, tenta processar seu conteúdo pelo caminho antigo .
-
{} +
é capaz de alimentar bash
com objetos múltiplos (ao contrário da sintaxe {} \;
). Nós iteramos sobre eles com for f in "$@"
. O objetivo não é executar um processo bash
separado para cada objeto, pois a criação de um novo processo é dispendiosa. Acho que não podemos evitar facilmente executar mv
-s; Ainda assim, reduzir o número de invocações bash
parece uma boa otimização ( pwd
é embutido em bash
e não nos custa um processo). No entanto, -execdir ... {} +
não passará arquivos de diretórios diferentes juntos. Ao usar -exec ... {} +
em vez de -execdir ... {} +
, podemos reduzir ainda mais o número de processos, mas precisamos nos preocupar com os caminhos, não apenas os nomes de arquivos (compare esta outra resposta , parece fazer um trabalho decente, mas while read
reduz a velocidade ). Esta é uma questão de velocidade versus simplicidade (relativa). Minha solução com -exec
está abaixo.
-
dummy
pouco antes de {}
se tornar $0
dentro de nosso bash
. Precisamos desse argumento fictício porque "$@"
é equivalente a "$1" "$2" ...
(não "$0" "$1" ...
). Dessa forma, tudo o que passou por {}
está disponível posteriormente como "$@"
.
Versão mais complexa e ligeiramente otimizada (vários ${...}
truques extraídos de outra resposta ):
find . -depth -name "* *" -exec \
bash -c '
for f in "$@"; do
n="${f##*/}"
mv -nv "$f" "${f%/*}/${n// /_}"
done
' dummy {} +
Outra abordagem (experimental!) envolve vidir
. O truque é vidir
usa $EDITOR
, o qual pode não ser um editor interativo :
find . -name "* *" | EDITOR='sed -i s/\d32/_/g' vidir -
Advertências:
- Isso falhará nos nomes de arquivos / diretórios com caracteres especiais (por exemplo, novas linhas).
- Não podemos usar
s/ /_/g
diretamente, \d32
é uma solução alternativa.
- Por causa de como
vidir
funciona, a abordagem seria complicada se você quiser substituir um dígito ou uma guia.
- Aqui
vidir
funciona com caminhos, não apenas nomes de arquivos (nomes de base), portanto, renomear arquivos apenas (ou seja, não diretórios) pode ser difícil.
No entanto, se você sabe o que está fazendo, isso pode ser ainda mais rápido. Eu não recomendo tal (ab) uso de vidir
no caso geral. Eu o incluí na minha resposta porque achei essa abordagem experimental interessante.