Sua solução é geralmente boa, mas vai quebrar em novas linhas. Aqui está uma solução bash4 + um pouco mais robusta:
shopt -s globstar nullglob
for file in **/*.txt; do
mv "$file" "${file%.*}.eml"
done
O diretório "$ d" contém alguns milhares de arquivos de e-mail com a extensão .txt. Para abri-los no meu cliente de e-mail, preciso renomeá-los para .eml
Este comando irá renomeá-los corretamente:
find "${d}" -type f -name '*.txt' | while read f; do mv -vn "${f}" "${f%.*}".eml; done
ou existe uma maneira melhor e mais robusta de fazer isso?
Eu não conseguia pensar em uma maneira elegante de fazer isso usando:
-exec ...{}... \;
Eu acho que você deve estar bem com
find "$d" -name \*.txt -exec rename .txt .eml {} \;
ou até mesmo
for f in *.txt; do rename .txt .eml "$f"; done
se todos os arquivos estiverem no mesmo diretório.
Sim, seu comando funcionará, supondo que você esteja usando bash ou um shell com sintaxe semelhante. No futuro, quando estiver contemplando um comando grande como esse, lembre-se de que você pode usar echo para visualizar as linhas de comando resultantes. Ou seja você pode colocar echo na frente de mv , executar o pipeline e ver quais serão os comandos. Se eles parecem OK, remova echo e execute o comando para real.
Em zsh, zmv torna isso fácil. Coloque autoload -U zmv no seu ~/.zshrc e, em seguida, use uma das várias maneiras de especificar um texto de substituição para **/*.txt que corresponda aos arquivos com a extensão txt no diretório atual e nos subdiretórios:
zmv '**/*.txt' '$f:r.eml'
zmv '**/*.txt' '${f%.*}.eml'
zmv '(**/)(*).txt' '$1$2.eml'
zmv -w '**/*.txt' '$1$2.eml'
Se você não tem zsh, mas tem bash ≥4 ou ksh93, você pode usar o ** para percorrer subdiretórios recursivamente e, em seguida, percorrer as correspondências. Você precisará ativar o padrão ** glob primeiro com shopt -s globstar no bash (coloque em ~/.bashrc ) ou set -o globstar no ksh (coloque em ~/.kshrc ). Isso também funciona em zsh (sem configuração prévia necessária).
for f in **/*.txt; do mv -- "$f" "${f%.*}.eml"; done
Observe que isso renomeia todos os arquivos, não apenas arquivos regulares. Se você tiver diretórios ou outros arquivos não regulares e quiser deixá-los intocados:
zmv -Q '**/*.txt(.)' '$f:r.eml'
for f in **/*.txt; do [[ -f $f ]] && mv -- "$f" "${f%.*}.eml"; done
Sem recurso de shell além de POSIX, você precisará invocar find para percorrer subdiretórios. Sua solução é frágil porque quebra arquivos contendo barras invertidas, espaços em branco ou linhas novas. Você pode corrigir o espaço em branco à direita e a barra invertida com while IFS= read -r f; do … , mas canalizando a saída de find inerentemente quebra em novas linhas. Use -exec em vez disso. No Linux, você pode usar o utilitário rename ( qualquer que seja sua distribuição ). No Debian, Ubuntu e derivados:
rename 's/\.txt$/.eml/' **/*.txt
find . -name '*.txt' -type f -exec rename 's/\.txt$/.eml/' {} +
Em outras distribuições, desde que nenhum dos nomes de arquivo contenha .txt no meio (porque rename substitui a primeira ocorrência da string de origem):
rename .txt .eml **/*.txt
find . -name '*.txt' -type f -exec rename .txt .eml {} +
Com apenas recursos POSIX, invoque um shell para executar a transformação no nome.
find . -name '*.txt' -type f -exec sh -c 'for f; do mv "$f" "${f%.*}.eml"; done' _ {} +
Se o find for muito antigo para suportar -exec … + , será necessário invocar um shell por arquivo, o que torna o código mais simples, mas mais lento.
find . -name '*.txt' -type f -exec sh -c 'mv "$0" "${0%.*}.eml"; done' {} \;