Como altero recursivamente a extensão de vários arquivos na linha de comando?

73

Eu tenho muitos arquivos com .abcextensão e quero alterá-los para .edefg
Como fazer isso na linha de comando?

Eu tenho uma pasta raiz com muitas subpastas, portanto a solução deve funcionar recursivamente.

tommyk
fonte

Respostas:

85

Uma maneira portátil (que funcionará em qualquer sistema compatível com POSIX):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

No bash4, você pode usar a globstar para obter globs recursivos (**):

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

O renamecomando (perl) no Ubuntu pode renomear arquivos usando a sintaxe de expressão regular perl, que você pode combinar com a globstar ou find:

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

Veja também http://mywiki.wooledge.org/BashFAQ/030

Geirha
fonte
o primeiro simplesmente acrescenta a nova extensão para o antigo, ele não substitui ...
user2757729
3
@ user2757729, substitui-o. "${1%.abc}"expande para o valor de $1exceto sem .abcno final. Veja o faq 100 para mais informações sobre manipulações de string de shell.
Geirha
Eu executei isso em uma estrutura de arquivos de ~ 70k ... pelo menos no meu shell, definitivamente não o substitui.
User2757729
11
@ user2757729 Você provavelmente deixou o "abc" em${1%.abc}...
kvnn
11
Caso alguém mais esteja se perguntando o que o sublinhado está fazendo no primeiro comando, é necessário, porque sh -c o primeiro argumento é atribuído a $ 0 e os argumentos restantes são atribuídos aos parâmetros posicionais . Portanto, _é um argumento fictício e '{}' é o primeiro argumento posicional a sh -c.
hellobenallan 9/04
48

Isso fará a tarefa necessária se todos os arquivos estiverem na mesma pasta

rename 's/.abc$/.edefg/' *.abc

Para renomear os arquivos recursivamente, use isto:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'
Chakra
fonte
8
Ou rename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abcem uma versão moderna do Bash.
Adam Byrtek
Muito obrigado Adam por me dar uma dica sobre como usar * .abc em pastas recursivamente!
Rafał Cieślak 19/04/11
Ótima dica, obrigado! Onde posso encontrar mais documentação sobre o pequeno pedaço da sintaxe do regex? Como sno começo, e que outras opções posso usar lá. Obrigado !
Anto 08/03
@ AdamByrtek Acabei de falhar com sua sugestão no Utopic. ('nenhum desses arquivos' ou algo parecido.)
Jonathan Y.
14

Um problema com as renomeações recursivas é que, independentemente do método usado para localizar os arquivos, ele passa o caminho inteiro para rename, não apenas o nome do arquivo. Isso dificulta a renomeação complexa em pastas aninhadas.

Eu uso finda -execdiração de para resolver este problema. Se você usar em -execdirvez de -exec, o comando especificado será executado no subdiretório que contém o arquivo correspondente. Então, ao invés de passar o caminho inteiro rename, ele apenas passa ./filename. Isso facilita muito escrever o regex.

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

Em detalhe:

  • -type f significa apenas procurar arquivos, não diretórios
  • -name '*.abc' significa apenas nomes de arquivos de correspondência que terminam em .abc
  • '{}'é o espaço reservado que marca o local em -execdirque o caminho encontrado será inserido. As aspas simples são necessárias, para permitir que ele lide com nomes de arquivos com espaços e caracteres de shell.
  • As barras invertidas após -typee -namesão o caractere de continuação de linha bash. Eu os uso para tornar este exemplo mais legível, mas eles não são necessários se você colocar seu comando em uma única linha.
  • No entanto, a barra invertida no final da -execdirlinha é necessária. Ele existe para escapar do ponto-e-vírgula, que encerra o comando executado por -execdir. Diversão!

Explicação do regex:

  • s/ início da regex
  • \.\/coincidir com o ./ principal que o -execdir passa. Use \ para escapar do. e / metacaracteres (nota: esta parte varia dependendo da sua versão do find. Veja o comentário do usuário @apollo)
  • (.+)corresponde ao nome do arquivo. Os parênteses capturam a correspondência para uso posterior
  • \.abc escape do ponto, combine o abc
  • $ ancorar a partida no final da string

  • / marca o final da parte "correspondente" da regex e o início da parte "substituir"

  • version1_ adicione este texto a cada nome de arquivo

  • $1referencia o nome do arquivo existente, porque o capturamos entre parênteses. Se você usar vários conjuntos de parênteses na parte "correspondente", poderá consultá-los aqui usando $ 2, $ 3 etc.
  • .abco novo nome do arquivo terminará em .abc. Não há necessidade de escapar do metacaractere de ponto aqui na seção "substituir"
  • / fim da regex

Antes

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

Depois de

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

Dica: renamea opção -n é útil. Ele executa uma execução a seco e mostra quais nomes serão alterados, mas não faz nenhuma alteração.

Christian Long
fonte
Obrigado pela explicação dos detalhes, mas estranhamente, quando tento isso, o --execdirnão passou na liderança, ./portanto a \.\/parte no regex pode ser omitida. Pode ser que eu esteja no OSX e não no ubuntu
apollo
5

Outra maneira portátil:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
aNeutrino
fonte
4
Bem-vindo ao Ask Ubuntu! Embora essa seja uma resposta valiosa, recomendo expandi-la (editando) para explicar como e por que esse comando funciona.
Eliah Kagan
0

Isto é o que eu fiz e funcionou da maneira que eu queria. Eu usei o mvcomando. Eu tinha vários .3gparquivos e queria renomeá-los todos para.mp4

Aqui está um breve guia para ele:

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

Que simplesmente varre o diretório atual, pega todos os arquivos .3gp e renomeia (usando o mv) para ren-name_of_file.mp4

KhoPhi
fonte
Isso será renomeado file.3gppara file.3gp.mp4, o que pode não ser o que a maioria das pessoas deseja.
Muru
@ muru, mas o princípio ainda existe. Simplesmente selecione qualquer extensão e especifique a extensão de destino para renomeá-la. O .3gpou .mp4aqui foram apenas para fins ilustrativos.
KhoPhi
A sintaxe é preferível, de uma linha e fácil de entender. No entanto, eu usaria basename "$i" .mp4para remover a extensão anterior em vez de "ren- $ i.mp4".
TaoPR 13/05
Verdadeiro. Bom ponto.
KhoPhi #
0

Eu encontrei uma maneira fácil de conseguir isso. Para alterar extensões de muitos arquivos de jpg para pdf, use:

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done
Pacífica
fonte
0

Eu usaria o comando mmv do pacote com o mesmo nome :

mmv ';*.abc' '#1#2.edefg'

Os ;corresponde a zero ou mais */e corresponde #1na substituição. O *corresponde a #2. A versão não recursiva seria

mmv '*.abc' '#1.edefg'
MvG
fonte
0

Renomeie arquivos e diretórios com find -execdir | rename

Se você vai renomear arquivos e diretórios, não apenas com um sufixo, esse é um bom padrão:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

A -execdiropção impressionante faz um cdno diretório antes de executar o renamecomando, ao contrário -exec.

-depth verifique se a renomeação ocorre primeiro nos filhos e, depois, nos pais, para evitar possíveis problemas com os diretórios-pai ausentes.

-execdir é necessário porque a renomeação não funciona bem com caminhos de entrada sem nome de base, por exemplo, o seguinte falha:

rename 's/findme/replaceme/g' acc/acc

A PATHinvasão é necessária porque -execdirtem uma desvantagem muito irritante: findé extremamente opinativa e se recusa a fazer qualquer coisa -execdirse você tiver algum caminho relativo em sua PATHvariável de ambiente, por exemplo ./node_modules/.bin, falhando com:

find: O caminho relativo './node_modules/.bin' está incluído na variável de ambiente PATH, que é insegura em combinação com a ação -execdir de find. Remova essa entrada de $ PATH

Consulte também: Por que o uso da ação '-execdir' é inseguro para o diretório que está no PATH?

-execdiré uma extensão de localização GNU para POSIX . renameé baseado em Perl e vem do renamepacote. Testado no Ubuntu 18.10.

Mudar o nome da solução alternativa à procura

Se seus caminhos de entrada não vierem find, ou se você já teve o suficiente do aborrecimento do caminho relativo, podemos usar alguns Perl lookahead para renomear com segurança diretórios como em:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

Eu não encontrei um análogo conveniente para -execdircom xargs: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

O sort -ré necessário para garantir que os arquivos vir após seus respectivos diretórios, desde caminhos mais longos vir após mais curtos com o mesmo prefixo.

Ciro Santilli adicionou uma nova foto
fonte