Como trocar números de nome de arquivo sem colisões?

6

Considere esta lista de arquivos:

$ touch index-{1,2,3,4,5,6,7,8,9}.txt

Se eu quiser alterá-los para baixo para que eles comecem em zero, é relativamente fácil :

$ rename --verbose 's/^index-([1-9])\.txt$/$1/; $_="index-" . ($_ - 1) . ".txt"' index-*.txt

Isso funciona porque man bash especifica que os globs são classificados em ordem alfabética, portanto, ele renomeia index-1.txt para index-0.txt antes renomear index-2.txt para index-1.txt .

Isso se divide se você quiser mudar para cima , ou se os números tiverem diferentes comprimentos :

$ touch index-{10,11}.txt
$ rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' index-*.txt
index-10.txt not renamed: index-11.txt already exists
index-11.txt renamed as index-12.txt
index-1.txt not renamed: index-2.txt already exists

Possíveis correções de longo prazo:

  • Uma opção rename para tentar reorganizar as operações até que não haja colisões.
  • Uma opção rename para mover os arquivos para um diretório temporário primeiro e, em seguida, movê-los de volta com o novo nome.
  • Uma opção rename para renomear arquivos em duas etapas, usando nomes exclusivos na primeira etapa que não colidirão com os nomes originais ou novos.
  • Uma maneira de fazer a classificação natural + reversão de Bash globs.

Existe uma maneira simples de contornar isso?

    
por l0b0 22.09.2012 / 12:04

5 respostas

2

A solução de @ l0b0 foi reescrita para melhor robustez:

printf '%s
printf '%s%pre%' index-*.txt |
  sort --zero-terminated --field-separator - --key 2rn |
  xargs -0r rename --verbose '
    s/^index-([0-9]+)\.txt$/$1/;
    $_="index-" . ($_ + 1) . ".txt"'
' index-*.txt | sort --zero-terminated --field-separator - --key 2rn | xargs -0r rename --verbose ' s/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"'

Sinta-se à vontade para incluir em sua solução e depois eu removerei minha resposta.

Note que essa e as soluções do @ l0bo são específicas do GNU, não do Unix (GNU's Not Unix).

    
por 22.09.2012 / 16:42
3

Existe um comando padrão pouco conhecido no Unix que pode nos ajudar a encontrar uma solução geral para encontrar em qual ordem uma série de renomeações deve ser feita: tsort

Digamos que tenhamos uma lista de renomeações a serem feitas em um arquivo chamado renames.txt (assumindo por causa da demonstração que seu nome não contém espaços em branco):

d a
e f
b e
a c

Como d deve ser renomeado para a , isso significa que a deve ser renomeado antes de d . Então, temos uma ordem de classificação parcial, que seria o contrário da ordem em que os arquivos deveriam ser renomeados.

tsort é a ferramenta para inferir uma ordem de classificação completa de uma lista de ordens de classificação parciais. Ele retornaria com um erro se houvesse um loop que nos ajudaria a detectar casos em que não há solução. Se aplicarmos tsort nessa entrada, isso nos dará:

b
d
e
a
f
c

O que diz que b deve ser renomeado após d após e . Podemos usar GNU tac (alguns sistemas também têm tail -r ) para reverter essa ordem:

c
f
a
e
d
b

Junte-se a ele com nossa lista de renomeações:

tsort renames.txt | tac | awk '
  NR==FNR {
    ren[$1] = $2
    next
  }
  $1 in ren {
    print "mv -i --", $1, ren[$1]
  }' renames.txt -

que nos dá:

mv -i -- a c
mv -i -- e f
mv -i -- d a
mv -i -- b e

que podemos então canalizar para sh para executar.

No entanto, observe que o código acima não é robusto, pois não verificamos o status de saída de tsort acima para detectar loops e os nomes de arquivos não devem conter nenhum caractere de shell especial.

A robificação é deixada como um exercício para o leitor; -)

    
por 22.09.2012 / 16:29
1

Isso funciona para o caso específico acima:

rename --verbose 's/^index-([0-9]+)\.txt$/$1/; $_="index-" . ($_ + 1) . ".txt"' $(printf '%s\n' index-*.txt | sort --field-separator - --key 2n | tac)

Mas:

  1. ele não manipula espaços em branco em nomes de arquivos, e
  2. seria muito mais complicado para nomes de arquivos arbitrários.
por 22.09.2012 / 12:07
1

Não é uma resposta direta à sua pergunta, pois você está mencionando bash, mas com zsh, você pode especificar a ordem de classificação e a classificação numérica com qualificadores de globbing.

$ echo index-*.txt
index-12.txt index-1.txt index-2.txt index-4.txt
$ echo index-*.txt(n)
index-1.txt index-2.txt index-4.txt index-12.txt
$ echo index-*.txt(nOn)
index-12.txt index-4.txt index-2.txt index-1.txt

Ou você pode definir sua própria ordem de classificação com uma função e usar index-*(o+that-function) .

Observe também que o zsh tem sua própria função rename builtin chamada zmv :

$ zmv -fvQ 'index-(<->).txt(n)' 'index-$(($1-1)).txt'
mv -- index-1.txt index-0.txt
mv -- index-2.txt index-1.txt
mv -- index-4.txt index-3.txt
mv -- index-12.txt index-11.txt

$ zmv -fvQ 'index-(<->).txt(nOn)' 'index-$(($1+1)).txt'
mv -- index-11.txt index-12.txt
mv -- index-3.txt index-4.txt
mv -- index-1.txt index-2.txt
mv -- index-0.txt index-1.txt
    
por 22.09.2012 / 12:51
-1

Para o contexto, uso o LapseIt Pro App para timelapse stuff & por alguma razão parou, então eu reiniciei o lapso de tempo, mas deu-lhe um nome de projeto diferente como: -

Proj10_img0000000x

Eu precisava renomear todos os arquivos 3000'ish para um projeto anterior Proj9_img0000000x & para alterar o número no final, para que o projeto tivesse um feed contínuo Name + Number para que eu pudesse renderizar todas as imagens como um vídeo de projeto, por exemplo

Proj10_img00000001 - > Proj10_img00003249 necessário para mudar para Proj9_img00004325 - > Proj9_img00007574

Então, em resumo, o resultado final é assim na pasta do projeto Proj9_img00000001 - > Proj9_img00007574

Se estiver usando o Mac OSX (estou usando o Mojave), você pode

  1. No localizador, selecione todos os arquivos para alterar
  2. Clique com o botão direito do mouse em uma seção de arquivo destacada
  3. Selecione Renomear 'Quantidade X' Itens
  4. Uma janela pop-up "Renomear itens do Finder" é aberta
  5. Esta janela pop-up permite que você renomeie & renumerar facilmente, bem como algumas outras opções que é útil.

Ainda estou vendo a linha de comando grep / sed / awk / cut & encontrar comandos, mas por enquanto o método acima funciona (eu precisava do vídeo feito o mais rápido possível para postar)

    
por 30.04.2019 / 12:07