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' {} \;