Como renomear vários arquivos usando o find

37

Quero renomear vários arquivos (file1 ... filen para file1_renamed ... filen_renamed) usando o comando findcommand:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

Mas obtendo este erro:

mv: cannot stat filename=./file1’: No such file or directory

Isso não está funcionando porque o nome do arquivo não é interpretado como variável do shell.

user164014
fonte

Respostas:

46

A seguir, é uma correção direta da sua abordagem:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

No entanto, isso é muito caro se você tiver muitos arquivos correspondentes, porque você inicia um shell novo (que executa a mv) para cada correspondência. E se você tiver caracteres engraçados em qualquer nome de arquivo, isso explodirá. Uma abordagem mais eficiente e segura é a seguinte:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

Ele também tem o benefício de trabalhar com arquivos com nomes estranhos. Se for findcompatível, isso pode ser reduzido para

find . -type f -name 'file*' -exec mv {} {}_renamed \;

A xargsversão é útil quando não estiver sendo usada {}, como em

find .... -print0 | xargs --null rm

Aqui rmé chamado uma vez (ou com muitos arquivos várias vezes), mas não para todos os arquivos.

Eu removi a basenamepergunta em você, porque provavelmente está errado: você mudaria foo/bar/file8para file8_renamed, não foo/bar/file8_renamed.

Edições (como sugerido nos comentários):

  • Adicionado encurtado findsemxargs
  • Etiqueta de segurança adicionada
Thomas Erker
fonte
No caso, o xé inútil: a find . -type f -name 'file*' -exec mv {} "{}_renamed" \; xargsversão tem a mesma eficiência do primeiro exemplo / /
Costas
O xque há apenas diretamente corrigir a abordagem do autor da questão.
Thomas Erker 5/09
2
(1) É muito perigoso usar {}diretamente em um sh -c "…"comando shell ( ) - você deve sempre transmiti-lo como argumento. (2) Nem todas as versões de findsuporte à {}_renamedconstrução. (3) Não entendo sua afirmação que xargsé útil para remover arquivos (ao contrário de renomeá-los).
G-Man diz 'Restabelecer Monica'
@ G-Man: A diferença com xargsnão é mvvs. rm, mas o uso de {}vs. sem. O primeiro é semelhante a mv file1 file1_renamed; mv file2 file2_renamedenquanto o segundo é rm file1 file2.
Thomas Erker 5/09
1
e se você desejasse alterar os arquivos _renamed de volta aos nomes originais, como você faria isso?
Mcmillab 20/09/18
17

Depois de tentar a primeira resposta e brincar um pouco, descobri que isso pode ser feito um pouco mais curto e menos complexo usando -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

Parece que também deve fazer exatamente o que você precisa.

Matijs
fonte
2
Com findimplementações que suportam -execdire {}não como um todo, também é a mais segura. Você pode querer adicionar um -ia mv(e -Tse o seu mvsuporte) #
Stéphane Chazelas
1
@ StéphaneChazelas ainda melhor, em vez de confiar em mvum prompt ou em adição a ele, você pode (sem dúvida, dependendo de sua implementação do findsuporte) também usar o -okdirque produzirá o comando a ser executado antes de executá-lo.
Matijs
Vale ressaltar que -depthtambém é uma boa idéia se você também tocar em nomes de diretório.
Ciro Santilli escreveu: 12/01
Argh, apenas descobriu que -execdirtem uma desvantagem muito chato, findse recusa a fazer qualquer coisa se PATHcontém os caminhos relativos ... askubuntu.com/questions/621132/... find: The relative path XXX is included in the PATH environment
Ciro Santilli新疆改造中心法轮功六四事件
6

Outra abordagem é usar um while readloop over findoutput. Isso permite o acesso a cada nome de arquivo como uma variável que pode ser manipulada sem ter que se preocupar com problemas adicionais de custo / segurança em potencial de gerar um sh -cprocesso separado usando finda -execopção de 's .

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

E se o shell sendo usado suportar a -dopção de especificar um readdelimitador, você poderá suportar arquivos com nomes estranhos (por exemplo, com uma nova linha) usando o seguinte:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done
John B
fonte
3

Quero expandir a primeira resposta e observe que isso não funcionará para acrescentar ao nome do arquivo, pois o ./prefixo do caminho está presente no argumento do nome do arquivo.

Modificando a resposta de Thomas Erker, considero esta uma abordagem mais genérica

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

Opções do xargs:

--nullIndica que cada argumento passado stdintermina com um caractere nulo ( \0). Dessa forma, o nome do arquivo pode conter espaços, caso contrário, cada palavra será ameaçada como um parâmetro diferente para o mvcomando.

-I replace-strCada ocorrência de replace-strserá substituída pelo argumento lido stdin. Portanto, você pode alterá-lo para outra string, se precisar.

Sdlion
fonte
Por que fazer em pringf "%f\0"vez de a print0?
slm
1
@sim, porque -print0produzirá ./prefixos se PATTERNcontiver metacaracteres de shell que atrapalham ao renomear para algo que prefixa os nomes originais. (por exemplo mudança de nome 0 - foo.txtpara 00 - foo.txt, 1 - bar.txta 01 - bar.txt, etc.)
das-g
2

Eu era capaz de fazer algo semelhante com o for, finde mv.

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

Ele encontra todos os config.ymlarquivos e os renomeia paraconfig.yml.bak

Varun Risbud
fonte
isso tem a vantagem de poder usar expansão variável, por exemplo. $ {i: r}. Boa resposta.
Salami
Sim, você pode praticamente colocar qualquer expressão no fore executar uma operação em massa.
Varun Risbud 13/08