Como adiciono cadeias alternadas a nomes de arquivos e renumerá-los em pares?

7

Usando um microscópio de alto rendimento, produzimos milhares de imagens. Digamos que nosso sistema os nomeie:

ome0001.tif
ome0002.tif
ome0003.tif
ome0004.tif
ome0005.tif
ome0006.tif
ome0007.tif
ome0008.tif
ome0009.tif
ome0010.tif
ome0011.tif
ome0012.tif
...

Gostaríamos de inserir c1 e c2 em relação ao valor numérico das imagens e, em seguida, alterar a numeração original para que cada% sucessiva co_de% e c1 tenha o mesmo número incremental, respeitando ordem numérica (1, depois 2 ... depois 9, depois 10) em vez de ordem alfanumérica (1, depois 10, depois 2 ...).

No meu exemplo, isso daria:

ome0001c1.tif
ome0001c2.tif
ome0002c1.tif
ome0002c2.tif
ome0003c1.tif
ome0003c2.tif
ome0004c1.tif
ome0004c2.tif
ome0005c1.tif
ome0005c2.tif
ome0006c1.tif
ome0006c2.tif
...

Não conseguimos fazer isso via linha de comando do terminal (biólogo falando ...).

Qualquer sugestão seria muito apreciada!

    
por Philippe P 28.09.2017 / 15:27

3 respostas

11

rename realiza renomeação em massa e pode fazer a aritmética de que você precisa.

Diferentes distribuições GNU / Linux possuem diferentes comandos chamados rename , com diferentes sintaxes e capacidades. No Debian, Ubuntu e alguns outros sistemas operacionais, rename é o utilitário de renomeação Perl prename . É bem adequado para essa tarefa.

Primeiramente, eu recomendo que você informe rename para mostrar o que ele faria, executando-o com o -n flag:

rename -n 's/\d+/sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)/e' ome????.tif

Isso deve mostrar:

rename(ome0001.tif, ome0001c1.tif)
rename(ome0002.tif, ome0001c2.tif)
rename(ome0003.tif, ome0002c1.tif)
rename(ome0004.tif, ome0002c2.tif)
rename(ome0005.tif, ome0003c1.tif)
rename(ome0006.tif, ome0003c2.tif)
rename(ome0007.tif, ome0004c1.tif)
rename(ome0008.tif, ome0004c2.tif)
rename(ome0009.tif, ome0005c1.tif)
rename(ome0010.tif, ome0005c2.tif)
rename(ome0011.tif, ome0006c1.tif)
rename(ome0012.tif, ome0006c2.tif)

Supondo que é isso que você quer, vá em frente e execute-o sem o sinalizador -n (ou seja, apenas remova -n ):

rename 's/\d+/sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)/e' ome????.tif

Esse comando é um pouco feio - embora ainda mais elegante do que usar um loop em seu shell - e talvez alguém com mais experiência em Perl do que eu tenha postado uma solução mais bonita.

Eu recomendo o tutorial do Oli Bulk renomeando arquivos no Ubuntu; a mais breve das introduções ao comando rename , para uma introdução suave à escrita dos comandos rename .

Como esse comando rename específico funciona:

Veja o que o s/\d+/sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2)/e faz:

  • O principal s significa procurar texto para substituir.
  • A expressão regular /\d+/ corresponde a um ou mais dígitos ( + ) ( \d ). Isso corresponde ao seu 0001 , 0002 e assim por diante.
  • O comando sprintf("%04dc%d", int(($& - 1) / 2) + 1, 2 - $& % 2) foi criado. $& representa a correspondência. / normalmente termina o texto de substituição, mas \/ faz um literal / (que é divisão, conforme detalhado abaixo).
  • O trailing /e significa avaliar o texto de substituição como código.
    (Tente executá-lo com apenas / em vez de /e no final, mas certifique-se de manter o -n flag! )

Assim, seus novos nomes de arquivos são os valores de retorno de sprintf("%04dc%d", int(($& - 1) \/ 2) + 1, 2 - $& % 2) . Então, o que está acontecendo lá?

  • sprintf retorna o texto formatado. O primeiro argumento é a string de formato na qual os valores são colocados. %04d consome o primeiro argumento e o formata como um inteiro de 4 caracteres de largura. %4d omitiria zeros à esquerda, portanto, %04d é necessário. Não sendo coberto por % , c significa apenas uma letra literal c . Então %d consome o segundo argumento e o formata como um inteiro (com formatação padrão).
  • int(($& - 1) / 2) + 1 subtrai 1 do número extraído do nome do arquivo original, divide por 2, trunca a parte fracionária ( int faz isso) e, em seguida, adiciona 1. Essa aritmética envia 0001 e 0002 a 0001 , 0003 e 0004 a 0002 , 0005 e 0006 a 0003 e assim por diante.
  • 2 - $& % 2 toma o restante da divisão do número extraído do nome do arquivo original por 2 ( % faz isso), que é 0 se for par e 1 se for ímpar. Em seguida, ele subtrai isso de 2. Essa aritmética envia 0001 a 1 , 0002 a 2 , 0003 a 1 , 0004 a 2 e assim por diante.

Por fim, ome????.tif é um glob que seu shell expande para uma lista de todos os nomes de arquivos no diretório atual que começam com ome , terminam em .tif e têm exatamente quatro caracteres entre.

Esta lista é passada para o comando rename , que tentará renomear (ou com -n , informar como renomear) todos os arquivos cujos nomes contenham uma correspondência com o padrão \d+ . / p>

  • A partir de sua descrição, não parece que você tenha arquivos nesse diretório nomeado dessa forma, mas com alguns caracteres sem dígitos.
  • Mas, se você fizer isso, poderá substituir \d+ por \d{4} na expressão regular que aparece nos comandos mostrados acima, para garantir que eles não sejam renomeados ou apenas inspecionar cuidadosamente a saída produzida com -n , que você deveria estar fazendo assim mesmo.
  • Eu escrevi \d+ em vez de \d{4} para evitar tornar o comando mais complexo do que o necessário. (Existem muitas maneiras diferentes de escrevê-lo.)
por Eliah Kagan 28.09.2017 / 17:12
6

Eu usei uma maneira de fazer isso no Bash com base na idéia de que, se o número no nome do arquivo for par, queremos dividi-lo por dois e adicionar c2 , e se o número for ímpar, queremos adicione um a ele e divida por dois, e adicione c1 . Tratar arquivos ímpares e até numerados separadamente como esse é muito mais demorado do que o método Bash de Eliah Kagan e eu concordo que usar rename as em essa outra resposta de Eliah Kagan é a maneira mais inteligente, mas esse tipo de abordagem pode ser útil em algumas situações.

Uma ligeira vantagem para isso, sobre o uso de um intervalo como {0000...0012} é que ele só tenta operar em arquivos existentes, por isso não vai reclamar se os arquivos não existirem. No entanto, você ainda recebe arquivos numerados ilogicamente se houver alguma lacuna. Veja a segunda parte da minha resposta para uma maneira que não tem esse problema.

Em uma linha, parece horrível:

for f in *; do g="${f%.tif}"; h="${g#ome}"; if [[ $(bc <<< "$h%2") == 0 ]]; then printf -v new "ome%04dc2.tif" "$(bc <<< "$h/2")" ; echo mv -vn -- "$f" "$new"; else printf -v new "ome%04dc1.tif" "$(bc <<< "($h+1)/2")"; echo mv -vn -- "$f" "$new"; fi; done

Aqui está isso como um script:

#!/bin/bash

for f in *; do 
    g="${f%.tif}"
    h="${g#ome}"

    if [[ $(bc <<< "$h%2") == 0 ]]; then 
         printf -v new "ome%04dc2.tif" "$(bc <<< "$h/2")"
         echo mv -vn -- "$f" "$new"
    else
         printf -v new "ome%04dc1.tif" "$(bc <<< "($h+1)/2")"
         echo mv -vn -- "$f" "$new"
    fi
done

As declarações echo prepending the mv são apenas para teste. Remova-os para realmente renomear os arquivos, se você está vendo o que você quer que seja feito.

Notas

g="${f%.tif}"     # strip off the extension
h="${g#ome}"      # strip off the letters... now h contains the number

Teste que o número é par (ou seja, dividir por 2 não dá nenhum resto)

if [[ $(bc <<< "$h%2") == 0 ]]; then 

Eu usei bc , que não tentará tratar números com zeros à esquerda como números octais, embora eu possa ter acabado de remover os zeros com outra expansão de string já que vou formatar os números fixos largura de qualquer maneira.

Em seguida, construa o novo nome para os arquivos com numeração par:

printf -v new "ome%04dc2.tif" "$(bc <<< "$h/2")"

%04d será substituído pelo número de saída por bc <<< "$h/2" no formato de 4 dígitos, preenchido com zeros à esquerda (portanto, 0 = 0000, 10 = 0010, etc).

Renomeie o arquivo original com o novo nome construído

echo mv -vn -- "$f" "$new"

-v para verbose, -n para no-clobber (não sobrescreva arquivos que já tenham o nome pretendido, se existirem) e -- para evitar erros de nomes de arquivos que começam com - (mas desde o resto do meu script espera que seus arquivos sejam nomeados ome[somenumber].tif , eu acho que estou adicionando isso por hábito).

Preenchendo as lacunas

Após alguns ajustes e mais ajuda de Eliah Kagan, trabalhei de maneira mais sucinta para incrementar os nomes que tem a vantagem de preencher as lacunas. O problema dessa forma é que apenas incrementa um número, faz uma simples aritmética nesse número, formata-o e o coloca no nome do arquivo. Bash pensa (por assim dizer) "ok, aqui está o próximo arquivo, eu darei o próximo nome", sem prestar atenção ao nome do arquivo original. Isso significa que ele cria novos nomes que não estão relacionados aos nomes antigos , portanto você não poderá desfazer logicamente a renomeação, e os arquivos serão renomeados na ordem correta somente se seus nomes forem já tal que eles serão processados na ordem correta. Esse é o caso em seu exemplo, que tem números preenchidos com zero de largura fixa, mas se você tivesse arquivos nomeados, digamos, 2 , 8 , 10 , 45 , eles seriam processados no pedido 10 , 2 , 45 , 8 , que provavelmente não é o que você deseja.

Se essa abordagem for adequada para você, considerando tudo isso, você pode fazer assim:

i=0; for f in ome????.tif; do ((i++)); printf -v new "ome%04dc%d.tif" $(((i+1)/2)) $(((i+1)%2+1)); echo mv -vn "$f" "$new"; done 

ou

#!/bin/bash
i=0

for f in ome????.tif; do 
    ((i++))
    printf -v new "ome%04dc%d.tif" $(((i+1)/2)) $(((i+1)%2+1))
    echo mv -vn "$f" "$new"
done 

Notas

  • i=0 inicia uma variável
  • ((i++)) incrementa a variável por um (isso conta as iterações do loop)
  • printf -v new coloca a seguinte declaração na variável new
  • "ome%04dc%d.tif" o novo nome do arquivo com os formatos numéricos que serão substituídos pelos números mencionados posteriormente
  • $(((i+1)/2)) o número de vezes que o loop foi executado mais um, dividido por 2

    Isso funciona na base de que o Bash só faz divisão inteira, então quando dividimos um número ímpar por 2, obtemos a mesma resposta que recebemos quando dividimos o número par precedente por 2:

    $ echo $((2/2))
    1
    $ echo $((3/2))
    1
    
  • $(((i+1)%2+1)) O restante após dividir o número de vezes que o loop foi executado mais um por dois, mais um. Isso significa que, se o número da iteração for ímpar (por exemplo, a primeira execução), a saída será 1 e, se o número da iteração for par (por exemplo, a segunda execução), a saída será 2 , dando c1 ou c2
  • Eu usei i=0 porque, a qualquer momento durante a execução, o valor de i será o número de vezes que o loop foi executado , o que pode ser útil para depuração, também será o número ordinal do arquivo que está sendo processado (ou seja, quando i=69 , estamos processando o 69º arquivo). No entanto, podemos simplificar a aritmética começando com um% diferente co_de%, por exemplo:

    i=2; for f in ome????.tif; do printf -v new "ome%04dc%d.tif" $((i/2)) $((i%2+1)); echo mv -vn "$f" "$new"; ((i++)); done 
    

    Existem muitas maneiras de fazer isso:)

  • i apenas para testes - remova se você vir o resultado desejado.

Veja um exemplo do que esse método faz:

$ ls
ome0002.tif  ome0004.tif  ome0007.tif  ome0009.tif  ome0010.tif  ome0012.tif  ome0019.tif  ome0100.tif  ome2996.tif
$ i=0; for f in ome????.tif; do ((i++)); printf -v new "ome%04dc%d.tif" $(((i+1)/2)) $(((i+1)%2+1)); echo mv -vn "$f" "$new"; done 
mv -vn ome0002.tif ome0001c1.tif
mv -vn ome0004.tif ome0001c2.tif
mv -vn ome0007.tif ome0002c1.tif
mv -vn ome0009.tif ome0002c2.tif
mv -vn ome0010.tif ome0003c1.tif
mv -vn ome0012.tif ome0003c2.tif
mv -vn ome0019.tif ome0004c1.tif
mv -vn ome0100.tif ome0004c2.tif
mv -vn ome2996.tif ome0005c1.tif
    
por Zanna 29.09.2017 / 00:46
5

Você pode escrever um loop de shell para isso, se você realmente quiser.

Se você quiser um comando que funcione em sistemas que não possuam rename ou cujo comando rename não seja prename , ou você queira que ele seja mais facilmente compreendido por pessoas que conhecem Bash, mas não Perl , ou por algum outro motivo você deseja implementar isso como um loop no seu shell que chama o comando mv , você pode. (Caso contrário, eu recomendo o método rename em minha outra resposta sobre isso.)

O Ubuntu tem o Bash 4, no qual expansão de chave preserva zeros à esquerda, então {0001..0012} expande para 0001 0002 0003 0004 0005 0006 0007 0008 0009 0010 0011 0012 . Isso é apropriado apenas em situações em que você realmente tem todos os arquivos em um intervalo. Com base na descrição do problema em sua pergunta, esse parece ser o caso. Caso contrário, ainda funcionaria, mas você receberia um monte de mensagens de erro para as lacunas, o que tornaria difícil perceber quaisquer outros erros que possam ser realmente importantes. Substitua 0012 pelo seu limite superior real.

Como echo aparece antes de mv , este comando apenas imprime os comandos mv que seria executado sem executá-los: 1

for i in {0001..0012}; do echo mv -n "ome$i.tif" "$(printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))")"; done

Isso usa a mesma idéia básica de minha rename answer , tanto quanto a aritmética, e para o significado de %04d e %d nas cadeias de formato. Isso poderia ser feito com {1..12} , mas seria ainda mais complicado porque exigiria duas substituições de comando $( ) com printf , em vez de apenas uma.

Tenha em mente que -n in rename -n não significa a mesma coisa que -n in mv -n . A execução de rename -n não move arquivos. A execução de mv -n move arquivos, a menos que tenha que substituir um arquivo existente no destino para fazer isso, o que significa que mv -n oferece a segurança que você obtém automaticamente com rename (a menos que você execute rename -f ). Para que o comando mostrado acima mova os arquivos, remova o echo :

for i in {0001..0012}; do mv -n "ome$i.tif" "$(printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))")"; done

Veja como esse loop de Bash funciona:

for i in {0001..0012} executa os comandos após do doze vezes, com i assumindo um valor diferente a cada vez. Esse loop só tem um tal comando antes de done , que significa o fim do corpo do loop. (Conceitualmente, quando o controle atinge done , ele passa para a próxima iteração do loop, com i como o próximo valor.) Esse comando é:

mv -n "ome$i.tif" "$(printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))")"
  • $i aparece algumas vezes no loop. Isso é expansão de parâmetro e é substituído pelo valor atual de i .
  • ome$i.tif expande para% de co_de%, ome0001.tif , ome0002.tif , etc., dependendo de qual valor ome0003.tif possui. Incluindo os 0s iniciais, escrevendo i em vez de {0001..0012} , faz este argumento para {1..12} , o que dá o nome antigo do arquivo, simples de escrever.
  • mv $( é substituição de comando . Dentro dele eu corro o comando ) que gera o texto desejado do segundo argumento para printf , que dá o novo nome do arquivo. A coisa toda é incluída em mv " quotes , portanto, expansões indesejadas - especificamente, globbing e divisão de palavras - são evitadas. Na substituição do comando, " é substituído por saída produzido executando o comando $(...) .

O comando que gera o nome do arquivo de destino é assim:

printf 'ome%04dc%d.tif' "$(((10#$i - 1) / 2 + 1))" "$((2 - 10#$i % 2))"
  • ... e %04d têm o mesmo significado da função %d do Perl que usou de sprintf .
  • Cada um dos dois argumentos usa expansão aritmética para realizar cálculos. Todo o rename é substituído pelo resultado da avaliação da expressão $((...)) .
  • ... assume o valor de 10#$i ( i ) e trata disso como um número base-10 ( $i ). Isso é necessário aqui porque o Bash trata os números com 10# s como octal . 2 Dentro de 0 $(( , você pode escrever o nome de uma variável para computá-la (ou seja, )) em vez de i ), mas $i também é suportado e $i é um dos poucos casos em que é necessário dentro de 10#$i $(( .
  • A aritmética aqui é a mesma que eu usada em )) , exceto que a divisão no Bash é automaticamente uma divisão inteira - automaticamente trunca a parte fracionária - portanto, não é necessário usar nada correspondente à função rename do Perl.

1 Um erro no destaque da sintaxe usado para o código Bash neste site faz com que tudo, após o int , fique esmaecido. Um # sem recursos geralmente inicia um comentário no Bash, embora neste caso, não é . Você não precisa se preocupar com isso - seu interpretador Bash não cometerá o mesmo erro.

2 O Perl também trata os números com # s como octal, também. No entanto, com 0 , a variável de correspondência rename é na verdade uma string - isso é processamento de texto , afinal. Perl permite que as strings sejam usadas como se fossem números, e quando isso acontece, levar $& s na string não faz com que seja tratado como um número octal! Comparar a maneira 0 com este método de loop de shell mais longo, mais difícil e menos robusto traz uma observação comum: Perl é estranho, mas faz o trabalho.

    
por Eliah Kagan 28.09.2017 / 22:19