Retira nomes de arquivo e renomeia

6

Que comando de renomeação posso usar que excluirá os nomes de arquivos presentes em um diretório e os substituirá por nomes de arquivos alfanuméricos?

Como gg0001 , gg0002 , et cetera? E gostaria que o comando trabalhasse em arquivos com qualquer tipo de extensão. E gostaria que a extensão fosse mantida.

Eu prefiro que o Perl rename seja usado.

Eu tentei este comando, mas ele apenas preenche o "gg" para os nomes de arquivos, enquanto eu quero os nomes de arquivos originais substituídos:

rename 's/^/gg_/' *

    
por user8547 08.11.2014 / 18:18

4 respostas

6

Você realmente não precisa do comando rename para isso, você pode fazer isso diretamente no shell:

c=0; for i in *; do let c++; mv "$i" "gg_$(printf "%03d" $c)${i#"${i%.*}"}"; done

O printf "%03d" $c imprimirá a variável $c , preenchendo-a com 3 0s iniciais, conforme necessário. O let c++ incrementa o contador ( $c ). O ${i#"${i%.*}"} extrai a extensão. Mais sobre aqui .

Eu não usaria rename para isso. Eu não acho que isso possa fazer cálculos. Construções válidas do Perl como s/.*/$c++/e falham e não vejo outra maneira de fazer cálculos. Isso significa que você ainda precisa executá-lo em um loop e ter algo mais para incrementar o contador para você. Algo como:

c=0;for i in *; do let c++; rename -n 's/.*\.(.*)/'$c'.$1/' "$i"; done

Mas não há vantagem em usar a abordagem mais simples de mv .

    
por 08.11.2014 / 19:05
4

Desde que haja uma ausência de ' aspas simples nos nomes dos arquivos, você poderá fazer o seguinte:

i=0; set --
for f in *
do set -- "$@" "$f" gg "$((i+=1))" "${f#"${f%.*}"}"
done
printf "mv '%s' '%s%03d%s'\n" "$@" | sh

Dessa forma, se um nome de arquivo contiver um . , então, o que quer que seja seguido, será retido. Caso contrário, esse campo estará vazio e printf não imprime nada lá.

Suponho que mesmo que existam aspas simples nos nomes dos arquivos, seria possível fazer ...

i=0; set --
for f in *
do set -- "$@" "$((i+=1))" gg "$i" "$i#\"\${$i%.*}\""
done
printf 'mv "${%d}" "%s%03d${%s}"\n' "$@" | sh -s -- *

... o que provavelmente é muito mais seguro em geral.

Mas você sabe, depois de pensar sobre isso, eu cheguei a suspeitar que ambos são muito trabalhosos. A única razão pela qual eu canalizo uma das listas é evitar fazer as verificações do preenchimento com zero. Eu prefiro construir uma lista em um subshell e fazer o stream com um write() para um único subshell quando necessário.

Mas, dito isso, o preenchimento zero não pode ser muito difícil. Eu trabalhei um pouco com isso e eu criei esta pequena declaração de matemática:

$((z/=(m*=((i+=1)<$m?1:10))/$m))

Se colocado em um loop de iteração, ele aumentará $i uma vez por item, avançará o multiplicador $m em um back-off exponencial para cada fator de dez e o dízimo $z para cada um dos mesmos. A ideia é obter o valor em $z antes de iniciar um loop que detenha o maior incremento possível que $i atingirá. Isso é feito simplesmente:

set -- *; max=$# z=1
until [ "${#z}" -gt "${#max}" ]; do : "$((z*=10))"; done

Essa é uma maneira totalmente automatizada - ela será automaticamente preenchida com qualquer número que possa ser incrementado. Mas, honestamente, $z precisa ser apenas um número começando com 1 e seguido apenas por zeros, então:

set -- *; z=1000

... iria para o campo de milhares. Enfim, aqui está uma demonstração que usei ao testar:

time dash <<\CMD
    str=100000 z=1 m=1 i=0 
    until [ "${#z}" -gt "${#str}" ]; do : "$((z*=10))"; done
    while : "$((z/=(m*=((i+=1)<$m?1:10))/$m))" &&
    case "$i" in (*[2-8]*|*9*[10]*|*1*[19]*) continue;;
    ([019]*) set -- "$@" "z: $z m: $m i: $i" "${z#?}$i";;esac
    do [ "$i" = "$str" ] && printf %s\n "$@" && break; done
CMD

A maior parte disso está filtrando a saída porque eu queria ter certeza de que continuaria trabalhando para contagens de itens grandes, mas não queria ler 100.000 resultados.

OUTPUT

z: 100000 m: 10 i: 1
000001
z: 100000 m: 10 i: 9
000009
z: 10000 m: 100 i: 10
000010
z: 10000 m: 100 i: 99
000099
z: 1000 m: 1000 i: 100
000100
z: 1000 m: 1000 i: 999
000999
z: 100 m: 10000 i: 1000
001000
z: 100 m: 10000 i: 9999
009999
z: 10 m: 100000 i: 10000
010000
z: 10 m: 100000 i: 99999
099999
z: 1 m: 1000000 i: 100000
100000
dash <<<''  0.32s user 0.00s system 99% cpu 0.320 total

$m deve ser inicializado para o primeiro estágio no qual você deseja que os zeros iniciais comecem a ser removidos - Acabei de m=1 acima. $z , como mencionado, armazena a maior ordem de grandeza. E $i conta os loops, então i=0 é provavelmente uma aposta segura. De qualquer forma, o resto só acontece. A ideia é apenas remover o 1 de $z e colocá-lo em qualquer lugar.

set -- $(seq 1000); m=1 i=0 z=10000
while [ "$#" -gt 0 ] && : "$((z/=(m*=((i+=1)<$m?1:10))/$m))"
do printf 'mv "%s" "lead_string_%s"\n' "$1" "${z#1}$i${1#"${1%.*}"}"
shift; done

Eu uso printf apenas para mostrar o que parece, embora, claro, o objetivo seja evitar a impressão de dados e, em vez disso, entregá-los no shell atual como um argumento preparado e delimitado. De qualquer forma, parece que:

mv "1" "lead_string_0001"
mv "2" "lead_string_0002"
mv "3" "lead_string_0003"
...
mv "9" "lead_string_0009"
mv "10" "lead_string_0010"
mv "11" "lead_string_0011"
...
mv "15" "lead_string_0015"
...
mv "96" "lead_string_0096"
...
mv "99" "lead_string_0099"
mv "100" "lead_string_0100"
mv "101" "lead_string_0101"
mv "102" "lead_string_0102"
...
mv "998" "lead_string_0998"
mv "999" "lead_string_0999"
mv "1000" "lead_string_1000"

E não faz diferença se é um loop for ou while que você usa. Então, com arquivos que você poderia fazer:

m=1 i=0 z=10000
for f in *; do : "$((z/=(m*=((i+=1)<$m?1:10))/$m))"
mv -- "$f" "lead_string_${z#1}$i${f#"${f%.*}"}"
done
    
por 08.11.2014 / 19:47
2

Este trabalhou para mim. (Mas o comando rename usado aqui é um perl rename ).

ls -1 -c | xargs rename -n 's/.*/our $i; sprintf("gg%04d", $i++)/e'

No entanto, o acima pressupõe que não há arquivos que tenham espaços em seus nomes. Caso contrário, ele quebra. Além disso, analisar a saída ls não é realmente uma boa ideia.

No entanto, como ele quebra quando há espaços nos nomes de arquivos, vamos tentar corrigir os problemas acima.

find -print0 | xargs -0 rename 's/.*/our $i; sprintf("gg%04d", $i++)/e'

A sintaxe acima funciona, mas ainda tem problemas. A questão é quando você tem sub-diretórios, ele irá renomear os subdiretórios também, o que é o que desejamos.

Para corrigir esse problema, podemos reformular nosso comando como @terdon sugere em seus comentários. O comando final modificado se parece com

find -maxdepth 1 -type f -print0 | xargs -0 rename 's/.*/our $i; sprintf("gg%04d", $i++)/e'

Teste

Eu tenho os seguintes arquivos dentro da minha pasta. Não me renomeie é um subdiretório que não queremos renomear.

ls
Don't rename me  file1  file with spaces  hello

Agora, eu executo o comando e recebo a saída abaixo.

find -maxdepth 1 -type f -print0 | xargs -0 rename -n 's/.*/our $i; sprintf("gg%04d", $i++)/e'
./file1 renamed as gg0000
./hello renamed as gg0001
./file with spaces renamed as gg0002

O -n flag pode ser removido se estivermos satisfeitos com o que vemos como a saída final.

Referências

link

    
por 08.11.2014 / 18:42
1

Apenas incremente uma variável em cada iteração. Como rename define use strict , você precisará usar uma variável global $::i (ou use no strict ou declare a variável com our ).

rename 'our $i; s/.*/gg_$i/; ++$i' *

Se você quiser preencher o número com zeros à esquerda, conte o número de arquivos. A lista de arquivos está em @ARGV .

rename 'our $i; my $width = length(scalar(@ARGV)); s/.*/sprintf("gg_%0${width}d", $i++)/e' *

Você pode testar se sua variável de contador está definida para executar o código apenas na primeira iteração.

rename 'our ($i, $width); if (!defined $width) {$width = length(scalar(@ARGV)); $i = 0;} s/.*/sprintf("gg_%0${width}d", $i++)/e' *

Em todos os casos acima, se você passar nomes de arquivos com uma parte do diretório (ou seja, se você passar nomes de arquivos contendo / - não acontecendo aqui com * ), altere s/.*/…/ para s/[^/]*\z/…/ .

    
por 09.11.2014 / 01:07