Renomeando um grande número de arquivos de imagem com o bash

16

Preciso renomear aprox. 70.000 arquivos. Por exemplo: de sb_606_HBO_DPM_0089000até sb_606_dpm_0089000etc.

O intervalo de números vai de 0089000a 0163022. É apenas a primeira parte do nome que precisa mudar. todos os arquivos estão em um único diretório e são numerados sequencialmente (uma sequência de imagens). Os números devem permanecer inalterados.

Quando eu tento isso no bash, fico gritando que a "lista de argumentos é muito longa".

Editar:

Tentei renomear um único arquivo com mv:

mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx

Tentei renomear um intervalo (aprendi aqui na semana passada como mover uma carga de arquivos, então pensei que a mesma sintaxe poderia funcionar para renomear os arquivos ...). Eu acho que eu tentei o seguinte (ou algo parecido):

mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
rico
fonte
4
Para os revisores : não acho que seja uma duplicata; a maioria das respostas da CLI na outra pergunta não funcionará aqui devido ao grande número de arquivos colidindo com o ARG_MAXlimite do shell . Como essa pergunta solicita explicitamente uma solução de linha de comando, soluções GUI (possivelmente iguais), como na outra pergunta, também não coincidem.
dessert
11
Eu não acho que isso seja uma bobagem, porque não há problema em ter mais de uma pergunta sobre renomear arquivos. Por favor, não vamos fechar a perguntas específicas contra recursos genéricos que realmente não respondê-las ...
Zanna
11
@rich Se você pode editar explicitamente o comando que tentou, seria mais claro que isso não é uma bobagem. (Isso nos mostra que você está ciente dessa abordagem.) Saúde.
Sparhawk
2
rica, sua pergunta não é uma bobagem, porque é uma pergunta específica. Não se preocupe com isso. Mais importante, após uma pergunta ter recebido várias respostas votadas, editá-la provavelmente não é uma boa ideia, porque suas edições podem tornar as respostas existentes menos válidas. Agora, sinto que minha resposta deve explicar por mv {1..2} {3..4}que não funciona, o que é um problema totalmente diferente de ARG_MAX... Todos os outros que responderam provavelmente sentirão o mesmo! Então, do meu ponto de vista, eu gostaria que você reverter sua última edição e, se você quiser, fazer uma nova pergunta inteiro sobre mving com faixas
Zanna
11
@ Sparhawk, o OP escreveu claramente, desde a primeira versão da pergunta, que o problema é o argument list too longerro. Não há necessidade de esclarecer mais, isso claramente não é uma bobagem, pois precisamos de uma solução alternativa para lidar com ARG_MAX e as respostas na duplicata proposta não fazem isso.
Terdon #

Respostas:

25

Uma maneira é usar findcom -exec, e a +opção Isso cria uma lista de argumentos, mas divide a lista em quantas chamadas forem necessárias para operar em todos os arquivos sem exceder a lista de argumentos máxima. É adequado quando todos os argumentos serão tratados da mesma maneira. Este é o caso com rename, embora não com mv.

Pode ser necessário instalar o Perl renomear:

sudo apt install rename

Então você pode usar, por exemplo:

find . -maxdepth 1 -exec rename -n 's/_HBO_DPM_/_dpm_/' {} +

Remova -napós o teste, para realmente renomear os arquivos.

Zanna
fonte
11

Vou sugerir três alternativas. Cada um é um comando simples de linha única, mas fornecerei variantes para casos mais complicados, principalmente no caso de os arquivos a serem processados ​​serem misturados com outros arquivos no mesmo diretório.

mmv

Eu usaria o comando mmv do pacote com o mesmo nome :

mmv '*HBO_DPM*' '#1dpm#2'

Observe que os argumentos são passados ​​como seqüências de caracteres, para que a expansão glob não ocorra no shell. O comando recebe exatamente dois argumentos e localiza os arquivos correspondentes internamente, sem limites rígidos no número de arquivos. Observe também que o comando acima pressupõe que todos os arquivos que correspondem ao primeiro glob devem ser renomeados. Claro que você é livre para ser mais específico:

mmv 'sb_606_HBO_DPM_*' 'sb_606_dpm_#1'

Se você tiver arquivos fora do intervalo de números solicitado no mesmo diretório, pode ser melhor usar os números de loop over dados mais abaixo nesta resposta. No entanto, você também pode usar uma sequência de invocações mmv com padrões adequados:

mmv 'sb_606_HBO_DPM_0089*'       'sb_606_dpm_0089#1'    # 0089000-0089999
mmv 'sb_606_HBO_DPM_009*'        'sb_606_dpm_009#1'     # 0090000-0099999
mmv 'sb_606_HBO_DPM_01[0-5]*'    'sb_606_dpm_01#1#2'    # 0100000-0159999
mmv 'sb_606_HBO_DPM_016[0-2]*'   'sb_606_dpm_016#1#2'   # 0160000-0162999
mmv 'sb_606_HBO_DPM_01630[01]?'  'sb_606_dpm_01630#1#2' # 0163000-0163019
mmv 'sb_606_HBO_DPM_016302[0-2]' 'sb_606_dpm_016302#1'  # 0163020-0163022

loop sobre números

Se você deseja evitar a instalação de qualquer coisa ou precisa selecionar por intervalo de números, evitando correspondências fora desse intervalo, e está preparado para aguardar 74.023 chamadas de comando, você pode usar um loop bash simples:

for i in {0089000..0163022}; do mv sb_606_HBO_DPM_$i sb_606_dpm_$i; done

Isso funciona particularmente bem aqui, pois não há lacunas na sequência. Caso contrário, convém verificar se o arquivo de origem realmente existe.

for i in {0089000..0163022}; do
  test -e sb_606_HBO_DPM_$i && mv sb_606_HBO_DPM_$i sb_606_dpm_$i
done

Observe que, em contraste com for ((i=89000; i<=163022; ++i))a expansão da cinta, lida com zeros à esquerda, pois alguns Bash lançam alguns anos atrás. Na verdade, uma alteração solicitada, por isso estou feliz em ver casos de uso.

Leitura adicional: Expanda a chave nas páginas de informações do Bash, principalmente na parte sobre {x..y[..incr]}.

loop sobre arquivos

Outra opção seria fazer um loop sobre um globo adequado, em vez de apenas fazer um loop no intervalo inteiro em questão. Algo assim:

for i in *HBO_DPM*; do mv "$i" "${i/HBO_DPM/dpm}"; done

Novamente, essa é uma mvchamada por arquivo. E novamente o loop contém uma longa lista de elementos, mas a lista inteira não é passada como argumento para um subprocesso, mas manipulada internamente pelo bash, para que o limite não cause problemas.

Leitura adicional: Expansão dos parâmetros do shell nas páginas de informações do Bash, documentando ${parameter/pattern/string}entre outros.

Se você deseja restringir o intervalo de números ao que você forneceu, adicione uma verificação para isso:

for i in sb_606_HBO_DPM_+([0-9]); do
  if [[ "${i##*_*(0)}" -ge 89000 ]] && [[ "${i##*_*(0)}" -le 163022 ]]; then
    mv "$i" "${i/HBO_DPM/dpm}"
  fi
done

Aqui ${i##pattern}remove a mais longa correspondência prefixo patternde $i. Esse prefixo mais longo é definido como qualquer coisa, depois um sublinhado e zero ou mais zeros. O último é escrito como *(0)um padrão glob extendido que depende da extglobopção que está sendo configurada. A remoção de zeros à esquerda é importante para tratar o número como base 10 e não 8. O +([0-9])argumento no loop é outro globo estendido, que corresponde a um ou mais dígitos, apenas no caso de você ter arquivos que iniciam o mesmo, mas não terminam em um número.

MvG
fonte
Obrigado! Isso funcionou como um sonho: para i em {0089000..0163022}; do mv sb_606_HBO_DPM_ $ i sb_606_dpm_ $ i; done - tive que adicionar a extensão do nome do arquivo para que funcionasse, mas fez exatamente o que eu queria e até entendi a sintaxe. Obrigado @MvG
rich
@rich: Feliz por poder ajudar - você e, espero, futuros visitantes também. Não se esqueça de aceitar a resposta mais útil. Você sempre pode alterar essa marca de seleção no futuro se algo melhor surgir.
MvG
10

Uma maneira de contornar o ARG_MAXlimite é usar o builtin do bash shell printf:

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'

Ex.

rename -n 's/HBO_DPM/dpm/' sb_*
bash: /usr/bin/rename: Argument list too long

mas

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'
rename(sb_606_HBO_DPM_0089000, sb_606_dpm_0089000)
.
.
.
rename(sb_606_HBO_DPM_0163022, sb_606_dpm_0163022)
chave de aço
fonte
7
find . -type f -exec bash -c 'echo $1 ${1/HBO_DPM/dpm}' _ {} \;
./sb_606_HBO_DPM_0089000 ./sb_606_dpm_0089000

findno diretório atual .para todos os arquivos -type fe fazer mudar o nome do arquivo encontrado $1com a substituição HBO_DPMcom dmp um por um-exec ... \;

substitua echopor mvpara renomear.

αғsнιη
fonte
6

Você pode escrever um pequeno script python, algo como:

import os
for file in os.listdir("."):
    os.rename(file, file.replace("HBO_DPM", "dpm"))

Salve isso como um arquivo de texto, como rename.pyna pasta em que os arquivos estão e, em seguida, com o terminal nessa pasta, vá:

python rename.py
Caveira de pedra
fonte
6

Você pode fazer arquivo por arquivo (pode levar algum tempo) com

sudo apt install util-linux  # if you don't have it already
for i in *; do rename.ul HBO_DPM dpm "$i"; done

Como o Perl renameusado em outras respostas, rename.ultambém tem uma opção -nou --no-actpara teste.

muclux
fonte
Eu editei seu comentário sobre a resposta de Zanna, edite a resposta de Zanna ou deixe um comentário.
Fosslinux
@ubashu que não foi um comentário na minha resposta - estava se referindo à -nbandeira que eu usei para testar e sugerindo que ela também pode ser usada rename.ul.
Zanna
3

Vejo que ninguém convidou meu melhor amigo sedpara a festa :). O seguinte forloop alcançará seu objetivo:

for i in sb_606_HBO_DPM*; do
  mv "$i" "$(echo $i | sed 's/HBO_DPM/dpm/')";
done

Existem muitas ferramentas para esse trabalho, selecione a que for mais compreensível para você. Este é simples e facilmente alterado para se adequar a este ou a outros fins ...

andrew.46
fonte
Concedido, não muito relevante nesse caso específico, mas isso falhará se algum dos nomes de arquivo contiver novas linhas. Menciono isso, já que a maioria (todas?) Das outras respostas é robusta e pode lidar com nomes de arquivos arbitrários ou apenas funcionar no esquema de nomeação de arquivos do OP.
Terdon #
... novas linhas, espaços, curingas, ... alguns dos quais podem ser evitados citando $ina substituição de comando, mas não há maneira fácil de lidar com uma nova linha à direita no nome do arquivo.
22418 muru
3

Como estamos dando opções, aqui está uma abordagem Perl. cdno diretório de destino e execute:

perl -e 'foreach(glob("sb_*")){rename $_, s/_HBO_DPM_/_dpm_/r}'

Explicação

  • perl -e: Executar o script dada por -e.
  • foreach(glob){}: execute o que estiver no { }resultado de cada glob.
  • glob("sb_*"): retorna uma lista de todos os arquivos e diretórios no diretório atual cujos nomes correspondem ao shell glob sb*.
  • rename $_, s/_HBO_DPM_/_dpm_/r: magia perl. $_é uma variável especial que contém cada elemento sobre o qual estamos iterando (no foreach). Então aqui, cada arquivo será encontrado. s/_HBO_DPM_/_dpm_/substitui a primeira ocorrência de _HBO_DPM_com _dpm_. Como é executado $_por padrão, é executado em cada nome de arquivo. Os /rmeios "aplicam essa substituição a uma cópia da sequência de destino (o nome do arquivo) e retornam a sequência modificada. renameFaz o que você esperaria: renomeia arquivos. Portanto, a coisa toda renomeará o nome do arquivo atual ( $_) para si mesmo com _HBO_DPM_substituído por _dpm_.

Você pode escrever a mesma coisa que um script expandido (e mais legível):

#! /usr/bin/env perl
use strict;
use warnings;

foreach my $fileName (glob("sb_*")){
  ## Copy the name to a new variable
  my $newName = $fileName;
  ## change the copy. $newName is now the changed version
  $newName =~ s/_HBO_DPM_/_dpm_/;
  ## rename
  rename $fileName, $newName;
}
Terdon
fonte
1

Dependendo do tipo de renomeação que você está visualizando, o uso do vidir com edição de várias linhas pode ser satisfatório.
No seu caso particular, você pode selecionar todas as linhas no seu editor de texto e remover a parte _ " HBO" dos nomes de arquivos com poucas teclas.

kraymer
fonte
sim, vi tem gloable encontrar e substituir.
Jasen
2
Você pode estender sua resposta e dar um exemplo de como atingir a meta do OP vidir?
dessert