Usando sed para renomear arquivos em massa


87

Objetivo

Altere esses nomes de arquivo:

  • F00001-0708-RG-biasliuyda
  • F00001-0708-CS-akgdlaul
  • F00001-0708-VF-hioulgigl

para estes nomes de arquivo:

  • F0001-0708-RG-biasliuyda
  • F0001-0708-CS-akgdlaul
  • F0001-0708-VF-hioulgigl

Código Shell

Testar:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/'

Executar:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/' | sh

Minha pergunta

Eu não entendo o código sed. Eu entendo o que o comando de substituição

$ sed 's/something/mv'

significa. E eu entendo de alguma forma as expressões regulares. Mas não entendo o que está acontecendo aqui:

\(.\).\(.*\)

ou aqui:

& \1\2/

O primeiro, para mim, parece que significa: "um único caractere, seguido por um único caractere, seguido por qualquer sequência de comprimento de um único caractere" - mas certamente há mais do que isso. Quanto à última parte:

& \1\2/

Eu não faço ideia.


Respostas:


149

Em primeiro lugar, devo dizer que a maneira mais fácil de fazer isso é usar os comandos prename ou rename.

No Ubuntu, OSX (pacote Homebrew rename, pacote MacPorts p5-file-rename) ou outros sistemas com renomeação perl (prename):

rename s/0000/000/ F0000*

ou em sistemas com renomeação de util-linux-ng, como RHEL:

rename 0000 000 F0000*

Isso é muito mais compreensível do que o comando sed equivalente.

Mas quanto à compreensão do comando sed, a página de manual do sed é útil. Se você executar man sed e pesquisar por & (usando o comando / para pesquisar), você encontrará um caractere especial em s / foo / bar / reposições.

  s/regexp/replacement/
         Attempt  to match regexp against the pattern space.  If success‐
         ful,  replace  that  portion  matched  with  replacement.    The
         replacement may contain the special character & to refer to that
         portion of the pattern space  which  matched,  and  the  special
         escapes  \1  through  \9  to refer to the corresponding matching
         sub-expressions in the regexp.

Portanto, \(.\)corresponde ao primeiro caractere, que pode ser referenciado por \1. Em seguida, .corresponde ao próximo caractere, que é sempre 0. Em seguida, \(.*\)corresponde ao restante do nome do arquivo, que pode ser referenciado por \2.

A string de substituição coloca tudo junto usando &(o nome do arquivo original) e \1\2que é todas as partes do nome do arquivo, exceto o segundo caractere, que era um 0.

Esta é uma maneira bastante enigmática de fazer isso, IMHO. Se por algum motivo o comando rename não estivesse disponível e você quisesse usar o sed para renomear (ou talvez estivesse fazendo algo muito complexo para renomear?), Ser mais explícito em sua regex o tornaria muito mais legível. Talvez algo como:

ls F00001-0708-*|sed 's/F0000\(.*\)/mv & F000\1/' | sh

Ser capaz de ver o que está realmente mudando em s / search / replacement / torna-o muito mais legível. Além disso, ele não continuará sugando caracteres de seu nome de arquivo se você acidentalmente executá-lo duas vezes ou algo assim.


1
no meu servidor RHEL, a sintaxe de renomeação seria "renomear 0000 000 F0000 *"
David LeBauer

1
É mais provável que renameseja um link "renomeado" . ou seja rename, foi "renomeado" de prename.. por exemplo, no Ubuntu: readlink -f $(which rename)saídas /usr/bin/prename... O renamemencionado por David é um programa totalmente diferente.
Peter.O de

1
Bem pensado, Peter. Eu atualizei a resposta para abordar os dois utilitários de renomeação.
Edward Anderson

3
Para depurar isso, remova o tubo em sh no final. Os comandos ecoarão na tela.
Ben Mathews

1
Tem certeza de que é um bom conselho para enviar dados aleatórios sh? isso é potencialmente perigoso, pois um código arbitrário pode ser executado (você está tratando os dados como código).
gniourf_gniourf

44

você teve sua explicação sed, agora você pode usar apenas o shell, sem a necessidade de comandos externos

for file in F0000*
do
    echo mv "$file" "${file/#F0000/F000}"
    # ${file/#F0000/F000} means replace the pattern that starts at beginning of string
done

1
Legal, mas você não pode fazer referências com parênteses.
Leonidas Tsampros

26

Escrevi um pequeno post com exemplos sobre renomeação em lote usando sedalguns anos atrás:

http://www.guyrutenberg.com/2009/01/12/batch-renaming-using-sed/

Por exemplo:

for i in *; do
  mv "$i" "`echo $i | sed "s/regex/replace_text/"`";
done

Se a regex contém grupos (por exemplo \(subregex\), você pode usá-los no texto de substituição como \1\, \2etc.


Observe que as respostas somente com links são desencorajadas (os links tendem a ficar obsoletos com o tempo). Considere editar sua resposta e adicionar uma sinopse aqui.
kleopatra

não é tão eficiente, mas realiza o trabalho em algumas centenas de arquivos. Votado.
Varun Chandak

21

A maneira mais fácil seria:

for i in F00001*; do mv "$i" "${i/F00001/F0001}"; done

ou, portavelmente,

for i in F00001*; do mv "$i" "F0001${i#F00001}"; done

Isso substitui o F00001prefixo nos nomes dos arquivos por F0001. créditos para mahesh aqui: http://www.debian-administration.org/articles/150


3
Você deve citar corretamente as interpolações de variáveis; mv "$i" "${i/F00001/F0001}". Mas +1
tripleee 01 de

7

O sedcomando

s/\(.\).\(.*\)/mv & \1\2/

significa substituir:

\(.\).\(.*\)

com:

mv & \1\2

apenas como um sedcomando normal . No entanto, os parênteses &e \nmarcadores mudam um pouco.

A string de pesquisa corresponde (e lembra como padrão 1) o único caractere no início, seguido por um único caractere, seguido pelo resto da string (lembrado como padrão 2).

Na string de substituição, você pode se referir a esses padrões correspondentes para usá-los como parte da substituição. Você também pode se referir a toda a parte correspondida como &.

Portanto, o que esse sedcomando está fazendo é criar um mvcomando baseado no arquivo original (para a fonte) e caractere 1 e 3 em diante, removendo efetivamente o caractere 2 (para o destino). Ele fornecerá uma série de linhas no seguinte formato:

mv F00001-0708-RG-biasliuyda F0001-0708-RG-biasliuyda
mv abcdef acdef

e assim por diante.


1
Esta foi uma boa explicação, mas pode ser útil apontar como você usa o comando sed com outros comandos para realmente renomear os arquivos. Por exemplo:ls | sed "s/\(.\).\(.*\)/mv & \1\2/" | bash
jcarballo

@jcarballo: é perigoso analisar ls, canalizar sede depois canalizar uma concha! está sujeito à execução arbitrária de código com nomes de arquivo forjados. O problema é que os dados devem ser tratados como dados, e aqui são normalmente serializados em código sem qualquer precaução. Gostaria que o paxdiablo pudesse excluir esta resposta, pois ela realmente não mostra uma boa prática. (Tropecei nessa pergunta porque um iniciante disparou aleatoriamente | shapós um comando que não funcionou e, depois de ver essa pergunta e as respostas, achou que funcionaria melhor - estou horrorizado!) :).
gniourf_gniourf

3

A barra invertida com parênteses significa, "enquanto corresponde ao padrão, segure o material que combina aqui." Posteriormente, no lado do texto de substituição, você pode obter esses fragmentos lembrados de volta com "\ 1" (primeiro bloco entre parênteses), "\ 2" (segundo bloco) e assim por diante.


1

Se tudo o que você realmente está fazendo é remover o segundo caractere, independentemente de qual seja, você pode fazer o seguinte:

s/.//2

mas seu comando está construindo um mvcomando e direcionando-o ao shell para execução.

Isso não é mais legível do que sua versão:

find -type f | sed -n 'h;s/.//4;x;s/^/mv /;G;s/\n/ /g;p' | sh

O quarto caractere é removido porque findantecede cada nome de arquivo com "./".


Eu gostaria que você pudesse excluir esta resposta. Embora talvez tenha sido bom no caso específico do OP, muitas pessoas veem respostas como essa e não entendem, e direcionam aleatoriamente para | shum comando que não funciona, na esperança de que funcione Melhor. É horrível! (além disso, essa não é uma boa prática). Espero que você entenda!
gniourf_gniourf

0

Os parênteses capturam cadeias de caracteres específicas para uso pelos números com barra invertida.


0
 ls F00001-0708-*|sed 's|^F0000\(.*\)|mv & F000\1|' | bash

Horrível! sujeito à execução de código arbitrário (talvez não no contexto específico da pergunta, mas há muitas pessoas vendo respostas como essa e tentando digitar aleatoriamente algo parecido, e é assustadoramente perigoso!). Eu gostaria que você pudesse deletar esta resposta (além disso, você tem outra boa aqui, que eu votei).
gniourf_gniourf

0

Aqui está o que eu faria:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done

Então, se isso parecer certo, acrescente | shao final. Então:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done | sh

0

Usando renomear perl (um deve ter na caixa de ferramentas):

rename -n 's/0000/000/' F0000*

Remova a -nchave quando a saída parecer boa para renomear de verdade.

Atenção Existem outras ferramentas com o mesmo nome que podem ou não ser capazes de fazer isso, então tome cuidado.

O comando rename que faz parte do util-linuxpacote não vai.

Se você executar o seguinte comando ( GNU)

$ rename

e você vê perlexpr, então esta parece ser a ferramenta certa.

Se não, para torná-lo o padrão (geralmente já é o caso) em Debiane derivado como Ubuntu:

$ sudo apt install rename
$ sudo update-alternatives --set rename /usr/bin/file-rename

Para archlinux:

pacman -S perl-rename

Para distros familiares RedHat:

yum install prename

O pacote 'prename' está no repositório EPEL .


Para Gentoo:

emerge dev-perl/rename

Para * BSD:

pkg install gprename

ou p5-File-Rename


Para usuários de Mac:

brew install rename

Se você não tiver esse comando com outra distro, pesquise seu gerenciador de pacotes para instalá-lo ou fazê-lo manualmente :

cpan -i File::Rename

A versão autônoma antiga pode ser encontrada aqui


homem renomear


Esta ferramenta foi originalmente escrita por Larry Wall, o pai do Perl.


-1
for i in *; do mv $i $(echo $i|sed 's/AAA/BBB/'); done

4
Bem-vindo ao SO. Por favor, considere adicionar uma explicação ao seu código. Isso ajudará outros usuários a entendê-lo.
Digvijay S

Essa resposta é boa, mas é uma resposta quase duplicada de uma resposta altamente votada acima.
Eric Leschinski
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.