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.