Renomear conjuntos de arquivos com múltiplas extensões (incl. compostas) no shell

Renomear conjuntos de arquivos com múltiplas extensões (incl. compostas) no shell

Tenho uma lista de conjuntos de arquivos com muitas extensões, mas nomes exclusivos.

filename-1.foo.001
...
filename-1.foo.020
filename-1.foo.baz
filename-1.foo.bar1-2.baz
...
filename-1.foo.bar7-8.baz

another_filename.foo.001
another_filename.foo.002
...
another_filename.foo.009
another_filename.foo.baz
another_filename.foo.bar1-2.baz
another_filename.foo.bar3-4.baz
another_filename.foo.bar4-5.baz
another_filename.foo.bar7-8.baz

yet.a.different.file.name.foo.001
yet.a.different.file.name.foo.002
...
yet.a.different.file.name.foo.287
yet.a.different.file.name.foo.baz
yet.a.different.file.name.foo.bar1-2.baz
yet.a.different.file.name.foo.bar3-4.baz
yet.a.different.file.name.foo.bar4-5.baz
yet.a.different.file.name.foo.bar7-8.baz

moreFileNaming.foo.001
...
moreFileNaming.foo.009
moreFileNaming.foo.baz
moreFileNaming.foo.bar1-2.baz
moreFileNaming.foo.bar3-4.baz
moreFileNaming.foo.bar4-5.baz
moreFileNaming.foo.bar7-8.baz

Gostaria de renomeá-los usando a saída de openssl rand -hex 8para obter um nome de arquivo aleatório para cada conjunto como este:

9874f7187c914ea9.foo.001
...
9874f7187c914ea9.foo.020
9874f7187c914ea9.foo.baz
9874f7187c914ea9.foo.bar1-2.baz
...
9874f7187c914ea9.foo.bar7-8.baz

2f54a0b6528e3927.foo.001
2f54a0b6528e3927.foo.002
...
2f54a0b6528e3927.foo.009
2f54a0b6528e3927.foo.baz
2f54a0b6528e3927.foo.bar1-2.baz
2f54a0b6528e3927.foo.bar3-4.baz
2f54a0b6528e3927.foo.bar4-5.baz
2f54a0b6528e3927.foo.bar7-8.baz

71ad0aa90148b2f5.foo.001
71ad0aa90148b2f5.foo.002
...
71ad0aa90148b2f5.foo.287
71ad0aa90148b2f5.foo.baz
71ad0aa90148b2f5.foo.bar1-2.baz
71ad0aa90148b2f5.foo.bar3-4.baz
71ad0aa90148b2f5.foo.bar4-5.baz
71ad0aa90148b2f5.foo.bar7-8.baz

3721323156e921b5.foo.001
...
3721323156e921b5.foo.009
3721323156e921b5.foo.baz
3721323156e921b5.foo.bar1-2.baz
3721323156e921b5.foo.bar3-4.baz
3721323156e921b5.foo.bar4-5.baz
3721323156e921b5.foo.bar7-8.baz

Eu tentei for name (*.(<->|baz|bar<->.baz) result=$(openssl rand -hex 16) && mv $name $result(isso pode não estar funcionando, como acontecia há várias iterações), mas quando funcionava, davacada arquivoum nome aleatório, só quero que cada conjunto permaneça com o mesmo nome, apenas aleatório e com o mesmo tamanho. Sha1sum ou qualquer outro método também funcionaria.

Como faço isso? Principalmente para os arquivos.foo.bar-*.baz ?

Se largarmos foo

3721323156e921b5.001
...
3721323156e921b5.009
3721323156e921b5.baz
3721323156e921b5.bar1-2.baz
3721323156e921b5.bar3-4.baz
3721323156e921b5.bar4-5.baz
3721323156e921b5.bar7-8.baz

Também ficaria bem. Mais algumas perguntas:

  1. como faço para direcionar desde o início do arquivo até .foo?
  2. como faço para repetir a variável criada, por exemplo, result=$(openssl rand -hex 8)para usá-la nas renomeações, e somente quando um conjunto for concluído, para atribuí-la novamente para fazer um loop até que o próximo conjunto seja concluído, etc.?

Obrigado!

Responder1

Existem várias partes deste problema:

  1. Decomponha cada nome de arquivo em uma parte base e extensões.
  2. Aplique uma transformação consistente à parte base de cada nome.
  3. Renomeie os arquivos de acordo com a transformação escolhida da peça base, preservando as extensões.

1. Decompondo nomes de arquivos

Não está completamente claro nos nomes de exemplo o que você considera ser a parte base de um nome de arquivo. O separador é evidentemente um ponto, mas num exemplo como yet.a.different.file.name.foo.bar1-2.baz, qual ponto? Você menciona uma tentativa de usar *.(<->|baz|bar<->.baz), que não trataria fooor bar1-2como uma extensão. Um ajuste que os permite como uma extensão é .(foo|<->|baz|bar<->(|-<->).baz). Então você pode quebrar o nome de um arquivo $fda seguinte maneira:

setopt extended_glob
base=${f%%(.(<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}

Alternativamente, se for aceitável definir a base como tudo, até e excluindo o primeiro .foo., a decomposição é mais simples:

base=${f%*.foo.*}; extensions=${f#$base}

2. Aplicando uma transformação consistente

Se você quiser aplicar uma transformação determinística, basta recalcular a cada vez. Por exemplo, você pode obter um resultado pseudoaleatório pegando um MAC do nome com uma chave secreta, usando sempre a mesma chave secreta.

secret=$(openssl rand -hex 32)
for … # Loop over the files as per (3.), set $base and $extensions as per (1.)
  new_base=${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}

(Observação: a chave secreta ficará visível para outros usuários se eles forem executados psenquanto opensslestiver em execução. Presumo que isso não seja um problema no seu caso, mas futuros leitores devem tomar cuidado com isso.)

Se quiser aplicar uma transformação aleatória, você precisa lembrar para onde cada base é mapeada. Existem duas maneiras de fazer isso:

  • Você pode agrupar arquivos por base e processar uma base por vez.
  • Você pode processar os arquivos um por um, mas lembre-se para onde cada base está mapeada e só gere um novo mapeamento se a base ainda não tiver sido vista.

O segundo método é mais fácil e o primeiro método não tem nenhuma vantagem particular, então mostrarei apenas o segundo método.

Construa ummatriz associativamapeando bases para novas bases.

typeset -A mapping
mapping=()
for … # Loop over the files as per (3.), set $base and $extensions as per (1.)
  if ((!$+mapping[$base])); then
    mapping[$base]=$(openssl rand -hex 8)
  fi
  new_base=$mapping[$base]

3. Renomeando os arquivos

Zsh vem com uma ferramenta muito útil para renomear arquivos:zmv. A transformação que você deseja fazer é suficientemente complexa para que o zmv não a torne trivial: tanto a decomposição do nome do arquivo quanto a transformação requerem trabalho adicional. Existem algumas pequenas vantagens no zmv, mesmo no seu caso. Em particular, o zmv apresentará um erro se houver um conflito (extremamente improvável devido a fatores aleatórios, a menos que você use comprimentos mais curtos). Porém, devido à dificuldade na transformação do nome, usar zmv é complicado e um loop simples é mais fácil de escrever.

Aqui está um trecho completo usando nomes aleatórios.

setopt extended_glob
typeset -A mapping
mapping=()
for f in *.(foo|<->|baz|bar<->(|-<->).baz); do
  base=${f%%(.(foo|<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}
  if ((!$+mapping[$base])); then
    mapping[$base]=$(openssl rand -hex 8)
  fi
  new_base=$mapping[$base]
  mv -i -- $f $new_base$extensions
done

Aqui está um trecho completo usando nomes determinísticos para um determinado valor de $secret.

setopt extended_glob
secret=$(openssl rand -hex 32)
for f in *.(foo|<->|baz|bar<->(|-<->).baz); do
  base=${f%%(.(foo|<->|baz|bar<->(|-<->).baz))#}; extensions=${f#$base}
  new_base=${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}
  mv -i -- $f $new_base$extensions
done

E aqui está um uso de uma linha zmvpara o caso determinístico, usando o primeiro .foo.para marcar o final da base. A -wbandeira ajuda na quebra.

autoload zmv
secret=$(openssl rand -hex 32)
zmv -w '*.foo.*' '${"$(openssl dgst -sha256 -hmac $secret <<<$1)"[-16,-1]}.foo.$2'

Usar zmv no caso randomizado é mais complicado porque precisamos preservar as informações de uma etapa de transformação para a próxima. Podemos simplesmente empacotar algum código em uma substituição de comando zmv … '$(…; if …; then mapping[$base]=…; …)'porque a atribuição estaria mappingdentro do subshell de substituição de comando e, portanto, só teria efeito dentro do subshell. No entanto, podemos usar uma atribuição de parâmetros condicional${name=word}, para definir mapping[$base]somente se não estiver definido.

typeset -A mapping; mapping=()
zmv -w '*.foo.*' '${mapping[${1}]=$(openssl rand -hex 16)}.foo.$2'

Usar zmv com uma decomposição que não aproveita .foo, como o exemplo mais complexo em (1.) acima, resulta em um código muito mais complexo. Apenas para fins de exemplo, aqui está uma invocação zmv para o caso determinístico, usando basecomo uma variável temporária para armazenar o nome base. Ele usa ${name::=word}para atribuir a uma variável durante a expansão do parâmetro e ${…}[0]para suprimir essa parte da expansão ( [0]pega a substring do 0º caractere, que não existe, pois o zsh começa a numerar os elementos da matriz e os caracteres da string em 1; algo como [2,1]também funcionaria ).

zmv  '*.(<->|baz|bar<->.baz)' '${${base::=${f%%(.(<->|baz|bar<->(|-<->).baz))#}}[0]}${"$(openssl dgst -sha256 -hmac $secret <<<$base)"[-16,-1]}.${f#$base}'

Responder2

Você poderia fazer algo como:

autoload -Uz zmv # best in ~/.zshrc
typeset -A rand
zmv '(*).foo(.*)' '${rand[$1]=$(openssl rand -hex 8)}$2'

ou '(*)(.foo.*)'para não cair .foo.

Para testar primeiro, adicione a -nopção (ensaio) ao zmv.

zmvé uma ferramenta de renomeação em lote implementada como uma função carregável automaticamente.

O primeiro argumento é um padrão glob estendido e o segundo argumento é uma string que sofre expansões de palavras que determina como os arquivos são removidos com $1, $2... referindo-se aos (...)s correspondentes no padrão.

${rand[$1]=$(cmd)}acima define o membro da matriz associativa para ochavesendo o que resta da extremidade direita .foo.da saída de cmdif foi previamente não definido, o que garante que você sempre obtenha o mesmo valor de um determinadochave.

informação relacionada