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

16

Eu preciso renomear aprox. 70.000 arquivos. Por exemplo: De sb_606_HBO_DPM_0089000 a sb_606_dpm_0089000 etc.

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

Quando eu tento isso no bash, grizzles em mim que a 'lista de argumentos é muito longa'.

Editar:

Primeiro tentei renomear um único arquivo com mv :

mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx

Então eu 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 penso que tentei o seguinte (ou algo parecido):

mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
    
por rich 19.05.2018 / 16:13

9 respostas

25

Uma maneira é usar find com -exec e a opção + . Isso constrói 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 de rename , embora não com mv .

Pode ser necessário instalar a renomeação Perl:

sudo apt install rename

Então você pode usar, por exemplo:

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

Remova -n após o teste para realmente renomear os arquivos.

    
por Zanna 19.05.2018 / 16:26
11

Vou sugerir três alternativas. Cada um é um simples comando 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 do mesmo nome :

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

Note que os argumentos são passados como strings, então a expansão glob não acontece no shell. O comando recebe exatamente dois argumentos e, em seguida, localiza arquivos correspondentes internamente, sem limites rígidos no número de arquivos. Observe também que o comando acima assume 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 solicitados no mesmo diretório, talvez seja melhor usar o loop sobre os números fornecidos 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 over numbers

Se você quiser evitar a instalação de qualquer coisa, ou precisar selecionar por intervalo de números evitando correspondências fora desse intervalo, e você está preparado para aguardar 74.023 invocações de comandos, 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, você pode querer 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 chave manipula os zeros iniciais desde o lançamento de alguns Bash há alguns anos. Na verdade, uma mudança que solicitei, estou feliz em ver casos de uso para ela.

Outras leituras: Expansão de contraventamentos as páginas de informação do Bash, particularmente a parte sobre {x..y[..incr]} .

repetição de arquivos

Outra opção seria fazer um loop sobre um glob apropriado, em vez de simplesmente fazer um loop sobre o intervalo inteiro em questão. Algo assim:

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

Novamente, essa é uma invocação de mv por arquivo. E novamente o loop é sobre uma longa lista de elementos, mas a lista inteira não é passada como um argumento para um subprocesso, mas manipulada internamente pelo bash, então o limite não causará problemas.

Outras leituras: Expansão do Parâmetro da Shell nas páginas de informação do Bash, documentando ${parameter/pattern/string} entre outras.

Se você quisesse restringir o intervalo de números ao que você forneceu, você poderia adicionar um cheque 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 o prefixo mais longo que corresponde a pattern de $i . O prefixo mais longo é definido como qualquer coisa, então um sublinhado e, em seguida, zero ou mais zeros. O último é escrito como *(0) , que é um padrão global estendido que depende na extglob opção definida. Remover zeros à esquerda é importante para tratar o número como base 10 e não como base 8. O +([0-9]) no argumento do loop é outro glob estendido, correspondendo a um ou mais dígitos, caso você possua arquivos que iniciem o mesmo, mas não terminar em um número.

    
por MvG 19.05.2018 / 21:05
10

Uma maneira de contornar o ARG_MAX limit é usar o printf :

do shell bash
printf '%s
rename -n 's/HBO_DPM/dpm/' sb_*
bash: /usr/bin/rename: Argument list too long
' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'

Ex.

printf '%s
printf '%s
rename -n 's/HBO_DPM/dpm/' sb_*
bash: /usr/bin/rename: Argument list too long
' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'
' 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)

mas

printf '%s%pre%' 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)
    
por steeldriver 19.05.2018 / 17:07
7
find . -type f -exec bash -c 'echo $1 ${1/HBO_DPM/dpm}' _ {} \;
./sb_606_HBO_DPM_0089000 ./sb_606_dpm_0089000

find no diretório atual . para todos os arquivos -type f e renomeie o arquivo encontrado $1 com a substituição de HBO_DPM por dmp um por um -exec ... \;

substitua echo por mv para executar renomeação.

    
por devWeek 19.05.2018 / 16:34
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 como um arquivo de texto como rename.py na pasta em que os arquivos estão e, em seguida, com o terminal nessa pasta:

python rename.py
    
por Stone Skull 20.05.2018 / 03:37
6

Você pode fazer isso 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

Assim como o Perl rename usado em outras respostas, rename.ul também tem uma opção -n ou --no-act para testes.

    
por muclux 19.05.2018 / 16:30
3

Eu vejo que ninguém convidou meu melhor amigo sed para a festa :). O seguinte for loop realizará 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 o que é mais compreensível para você. Este é simples e facilmente alterado para atender a este ou outros propósitos ...

    
por andrew.46 20.05.2018 / 05:57
3

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

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

Explicação

  • perl -e : execute o script fornecido por -e .
  • foreach(glob){} : execute o que estiver no { } em cada resultado do 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 : perl magic. $_ é uma variável especial que contém cada elemento sobre o qual estamos interagindo (no foreach ). Então, aqui, será cada arquivo encontrado. s/_HBO_DPM_/_dpm_/ substitui a primeira ocorrência de _HBO_DPM_ por _dpm_ . Ele é executado em $_ por padrão, portanto, ele será executado em cada nome de arquivo. O /r significa "aplique essa substituição a uma cópia da string de destino (o nome do arquivo) e retorne a string modificada. rename faz o que você espera: renomeia os arquivos. Então a coisa toda renomeará o arquivo atual nome ( $_ ) 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;
}
    
por terdon 22.05.2018 / 12:52
1

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

    
por kraymer 19.05.2018 / 18:25