Bash regex para renomear o conjunto de arquivos

4

Eu tenho que renomear um conjunto de arquivos, usando o comando rename (com uma expressão regular). Depois de algumas tentativas, não consigo encontrar uma expressão que obtenha o resultado esperado.

Eu tenho um padrão de arquivo assim:

prefix_some_name_other.txt

Todos os arquivos começam com a string " prefix_ " e terminam com " _other.txt ", e a parte algum_nome pode consistir em múltiplas palavras (alfanuméricas) separados por sublinhados. Então é possível ter:

prefix_one_name_other.txt
prefix_this_is_my_name_1_this1_other.txt

Eu preciso renomear os nomes de arquivos como estes:

other_one-name_datetime
other_this-is-my-name-1-this1_datetime

Em outras palavras:

  • É necessário excluir " prefix " (deixando o sublinhado)
  • O token " other " vai para o início do nome do arquivo
  • Em algum_nome , converta sublinhado (_) em traço (-)
  • O sublinhado no final do nome do arquivo (depois de algum_nome ) deve permanecer
  • É necessário excluir a extensão .txt , substituída por datetime .

O que eu tentei:

rename 's/fw_([a-z]+)_(\d)_(\w+\d)_(\w+)\.txt/$4_$1-$2-$3_'$datahora'/' *.txt

$datahora tem o valor datetime (testado). Isso funciona como esperado com

prefix_name_1_gnt1_other.txt

mas não com

prefix_other_name_2_gnt2_other.txt

Onde eu errei? De que outra forma eu poderia conseguir isso?

Suspenso, já que, por enquanto, não consigo encontrar um regex que funcione para todos os nomes de arquivo que tenho. Eu sei que o primeiro elemento na string é sempre prefix part, e o último elemento é other.txt parte da string. Portanto, é possível dividir a cadeia em uma matriz e obter os itens necessários para criar o novo nome. Na verdade, algo assim.

datahora="20140718-080000"
arrfiles=( *.txt )
for curfile in ${arrfiles[*]}
do
    arrparts=( ${curfile//_/ } )
    numitems=${#arrparts[*]}
    newname=""
    for (( c=1; c<numitems-1; c++ ))
    do
        newname+="${arrparts[c]}-"
    done
    newname=${newname%-}
    arrparts[numitems-1]=${arrparts[numitems-1]/.txt/}
    newname="${arrparts[numitems-1]}_${newname}_$datahora"
    echo "$curfile pasa a $newname"
    mv ${curfile} ${newname}
done

Depois disso, dou outra tentativa para @peterph suggestion, e finalmente faço algumas combinações de regex rename. Algumas coisas assim:

rename 's/_/-/g' *.txt
rename 's/^fw-(.*)-([^-]*)(\.txt)/$2.$1$3/' *.txt
rename 's/(\w+)\.(.*)(\.txt)/$1_$2_'$datahora'/' *.txt

Não sei qual é a melhor abordagem. Na minha opinião, a variante regex parece mais elegante, mas eu preciso de três operações de renomeação (acesso três vezes ao disco) para fazer o trabalho, enquanto a variante array só grava uma vez no disco.

O que você acha dessas duas soluções? ...

Obrigado de novo.

    
por Ferran 17.07.2014 / 21:16

1 resposta

2

A menos que seu rename possa aceitar vários comandos de substituição e , a raiz do nome do arquivo ( some_name ) pode conter mais de um sublinhado, você deve fazer isso em duas etapas: substituindo sublinhados por traços eb) (re) movendo porções nos nomes dos arquivos.

As expressões regulares que você está procurando podem ser, por exemplo:

rename 's/_/-/g' *.txt
rename 's/^prefix-(.*)-([^-]*).txt$/$2_$1_'$DATETIME'/' *txt

O primeiro faz o sublinhado para traçar as traduções enquanto o segundo faz a troca de raiz e sufixo e acrescenta o conteúdo da variável de ambiente DATETIME aos nomes. E omite o prefixo e a extensão, é claro.

A parte [^-]* corresponde a qualquer sequência que não contenha um traço. Caso o sufixo seja sempre o mesmo, você pode colocá-lo lá literalmente, como é o caso do prefixo (e vice-versa - se o prefixo puder variar, use ^[^-]*- para corresponder a qualquer string que não contenha um traço entre o início do nome do arquivo e (assim) o primeiro traço).

Se o seu rename suportar vários comandos, basta concatená-los:

rename 's/_/-/g;s/^prefix-(.*)-([^-]*).txt$/$2_$1_'$DATETIME'/' *txt
    
por 17.07.2014 / 22:59