Lote renomeando arquivos com Bash

Como o Bash pode renomear uma série de pacotes para remover seus números de versão? Eu tenho andado por aí com expr e %% , sem sucesso.

Exemplos:

Xft2-2.1.13.pkg torna-se Xft2.pkg

jasper-1.900.1.pkg torna jasper-1.900.1.pkg se jasper.pkg

xorg-libXrandr-1.2.3.pkg torna xorg-libXrandr.pkg se xorg-libXrandr.pkg

Você poderia usar o recurso de expansão de parâmetro do bash

 for i in ./*.pkg ; do mv "$i" "${i/-[0-9.]*.pkg/.pkg}" ; done 

Citações são necessárias para nomes de arquivos com espaços.

Se todos os arquivos estiverem no mesmo diretório, a seqüência

 ls | sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | sh 

fará o seu trabalho. O comando sed criará uma seqüência de comandos mv , que você pode enviar para o shell. É melhor primeiro executar o pipeline sem o trailing | sh | sh para verificar se o comando faz o que você quer.

Para recurse através de vários diretórios usar algo como

 find . -type f | sed -n 's/\(.*\)\(-[0-9.]*\.pkg\)/mv "\1\2" "\1.pkg"/p' | sh 

Observe que, no sed, a sequência de agrupamento de expressão regular é colchetes precedidos por uma barra invertida, \( e \) , em vez de colchetes simples ( e ) .

Eu farei algo assim:

 for file in *.pkg ; do mv $file $(echo $file | rev | cut -f2- -d- | rev).pkg done 

Supostamente todos os seus arquivos estão no diretório atual. Se não, tente usar encontrar como recomendado acima por Javier.

EDIT : Além disso, esta versão não usa nenhum recurso bash-específico, como os outros acima, o que leva a mais portabilidade.

melhor usar sed para isso, algo como:

 find . -type f -name "*.pkg" | sed -e 's/((.*)-[0-9.]*\.pkg)/\1 \2.pkg/g' | while read nameA nameB; do mv $nameA $nameB; done 

descobrir a expressão regular é deixado como um exercício (como lidar com nomes de arquivos que incluem espaços)

Aqui está um POSIX quase equivalente da resposta atualmente aceita . Isso negocia a expansão de parâmetro apenas ${variable/substring/replacement} do Bash para um que está disponível em qualquer shell compatível com o Bourne.

 for i in ./*.pkg; do mv "$i" "${i%-[0-9.]*.pkg}.pkg" done 

A expansão do parâmetro ${variable%pattern} produz o valor da variable com qualquer sufixo que corresponda ao pattern removido. (Há também ${variable#pattern} para remover um prefixo.)

Mantive o subpadrão -[0-9.]* Da resposta aceita, embora talvez seja enganador. Não é uma expressão regular, mas um padrão glob; por isso não significa “um traço seguido por zero ou mais números ou pontos”. Em vez disso, significa “um traço, seguido por um número ou um ponto, seguido por qualquer coisa”. O “qualquer coisa” será a correspondência mais curta possível, não a mais longa. (Bash oferece ## e %% para aparar o maior prefixo ou sufixo possível do que o mais curto).

Isso parece funcionar supondo que

  • tudo termina com $ pkg
  • sua versão # sempre começa com um “-”

tira o .pkg, depois tira – o ..

 for x in $(ls); do echo $x $(echo $x | sed 's/\.pkg//g' | sed 's/-.*//g').pkg; done 

Eu tinha vários arquivos *.txt para ser renomeado como .sql na mesma pasta. abaixo funcionou para mim:

 for i in \`ls *.txt | awk -F "." '{print $1}'\` ;do mv $i.txt $i.sql; done 

Obrigado por esta resposta. Eu também tive algum tipo de problema. Movendo arquivos .nzb.queued para arquivos .nzb. Havia espaços e outros arquivos nos nomes de arquivos e isso resolveu meu problema:

 find . -type f -name "*.nzb.queued" | sed -ne "s/^\(\(.*\).nzb.queued\)$/mv -v \"\1\" \"\2.nzb\"/p" | sh 

É baseado na resposta do Diomidis Spinellis.

O regex cria um grupo para o nome do arquivo inteiro e um grupo para a parte antes de .nzb.queued e, em seguida, cria um comando de movimentação do shell. Com as cordas citadas. Isso também evita a criação de um loop no shell script, porque isso já é feito por sed.

Podemos supor que o sed está disponível em qualquer * nix, mas não podemos ter certeza de que ele suportará sed -n para gerar comandos mv. ( NOTA: Somente o GNU sed faz isso.)

Mesmo assim, bash builtins e sed, podemos rapidamente agitar uma function de shell para fazer isso.

 sedrename() { if [ $# -gt 1 ]; then sed_pattern=$1 shift for file in $(ls $@); do mv -v "$file" "$(sed $sed_pattern < << $file)" done else echo "usage: $0 sed_pattern files..." fi } 

Uso

 sedrename 's|\(.*\)\(-[0-9.]*\.pkg\)|\1\2|' *.pkg before: ./Xft2-2.1.13.pkg ./jasper-1.900.1.pkg ./xorg-libXrandr-1.2.3.pkg after: ./Xft2.pkg ./jasper.pkg ./xorg-libXrandr.pkg 

Criando pastas de destino:

Como o mv não cria automaticamente pastas de destino, não podemos usar nossa versão inicial do sedrename .

É uma alteração relativamente pequena, por isso seria bom include esse recurso:

Nós precisaremos de uma function de utilidade, abspath (ou caminho absoluto), já que o bash não tem esse build.

 abspath () { case "$1" in /*)printf "%s\n" "$1";; *)printf "%s\n" "$PWD/$1";; esac; } 

Assim que tivermos, podemos gerar a (s) pasta (s) de destino para um padrão sed / rename que inclua uma nova estrutura de pastas.

Isso garantirá que nós sabemos os nomes das nossas pastas de destino. Quando renomeamos, precisaremos usá-lo no nome do arquivo de destino.

 # generate the rename target target="$(sed $sed_pattern < << $file)" # Use absolute path of the rename target to make target folder structure mkdir -p "$(dirname $(abspath $target))" # finally move the file to the target name/folders mv -v "$file" "$target" 

Aqui está o script ciente de pasta completa ...

 sedrename() { if [ $# -gt 1 ]; then sed_pattern=$1 shift for file in $(ls $@); do target="$(sed $sed_pattern < << $file)" mkdir -p "$(dirname $(abspath $target))" mv -v "$file" "$target" done else echo "usage: $0 sed_pattern files..." fi } 

Claro, ainda funciona quando não temos pastas de destino específicas também.

Se quiséssemos colocar todas as músicas em uma pasta, ./Beethoven/ , podemos fazer isso:

Uso

 sedrename 's|Beethoven - |Beethoven/|g' *.mp3 before: ./Beethoven - Fur Elise.mp3 ./Beethoven - Moonlight Sonata.mp3 ./Beethoven - Ode to Joy.mp3 ./Beethoven - Rage Over the Lost Penny.mp3 after: ./Beethoven/Fur Elise.mp3 ./Beethoven/Moonlight Sonata.mp3 ./Beethoven/Ode to Joy.mp3 ./Beethoven/Rage Over the Lost Penny.mp3 

Rodada de bônus ...

Usando este script para mover arquivos de pastas para uma única pasta:

Supondo que queríamos reunir todos os arquivos correspondentes e colocá-los na pasta atual, podemos fazer isso:

 sedrename 's|.*/||' **/*.mp3 before: ./Beethoven/Fur Elise.mp3 ./Beethoven/Moonlight Sonata.mp3 ./Beethoven/Ode to Joy.mp3 ./Beethoven/Rage Over the Lost Penny.mp3 after: ./Beethoven/ # (now empty) ./Fur Elise.mp3 ./Moonlight Sonata.mp3 ./Ode to Joy.mp3 ./Rage Over the Lost Penny.mp3 

Nota sobre padrões de regex de sed

As regras regulares do padrão sed são aplicadas neste script, esses padrões não são PCRE (Perl Compatible Regular Expressions). Você poderia ter a syntax de expressão regular estendida sed, usando sed -r ou sed -E dependendo da sua plataforma.

Veja o man re_format do POSIX para uma descrição completa dos padrões básicos e estendidos do sedex.

Acho que renomear é uma ferramenta muito mais simples de usar para esse tipo de coisa. Eu encontrei no Homebrew para OSX

Para o seu exemplo eu faria:

 rename 's/\d*?\.\d*?\.\d*?//' *.pkg 

O ‘s’ significa substituto. O formulário é s / searchPattern / replacement / files_to_apply. Você precisa usar o regex para isso, o que requer um pequeno estudo, mas vale a pena o esforço.