Renommer un grand nombre de fichiers image avec bash

16

Je dois renommer env. 70 000 fichiers. Par exemple: de sb_606_HBO_DPM_0089000à sb_606_dpm_0089000etc.

La plage de numéros va de 0089000à 0163022. Ce n'est que la première partie du nom qui doit changer. tous les fichiers sont dans un seul répertoire et sont numérotés séquentiellement (une séquence d'images). Les chiffres doivent rester inchangés.

Quand j'essaye ceci dans bash, cela me fait peur que la 'liste d'arguments soit trop longue'.

Éditer:

J'ai d'abord essayé de renommer un seul fichier avec mv:

mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx

Ensuite, j'ai essayé de renommer une plage (j'ai appris ici la semaine dernière comment déplacer une charge de fichiers, j'ai donc pensé que la même syntaxe pourrait fonctionner pour renommer les fichiers ...). Je pense que j'ai essayé ce qui suit (ou quelque chose comme ça):

mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
riches
la source
4
Aux critiques : je ne pense pas que ce soit un doublon; la plupart des réponses CLI sur l'autre question ne fonctionneront pas ici en raison du grand nombre de fichiers entrant en collision avec la ARG_MAXlimite du shell . Comme cette question demande explicitement une solution en ligne de commande, les solutions GUI (éventuellement égales) comme dans l'autre question ne correspondent pas non plus.
dessert
1
Je ne pense pas que ce soit une dupe parce que c'est ok d'avoir plus d'une question sur le changement de nom des fichiers. S'il vous plaît, ne fermons pas les questions spécifiques contre les ressources génériques qui n'y répondent pas réellement ...
Zanna
1
@rich Si vous pouvez modifier explicitement la commande que vous avez essayée, alors il serait plus clair que ce n'est pas dupe. (Cela nous montre que vous êtes au courant de cette approche.) Bravo.
Sparhawk
2
riche, votre question n'est pas dupe, car c'est une question spécifique. Ne vous en faites pas. Plus important encore, après qu'une question a reçu un certain nombre de réponses votées, la modifier n'est probablement pas une bonne idée car vos modifications peuvent rendre les réponses existantes moins valides. Maintenant, j'ai l'impression que ma réponse devrait expliquer pourquoi mv {1..2} {3..4}cela ne fonctionne pas, ce qui est un tout autre problème que ARG_MAX... Tous ceux qui ont répondu ressentiront probablement la même chose! Donc, de mon point de vue, j'aimerais que vous annuliez votre dernière édition et, si vous le souhaitez, posez une toute nouvelle question sur l' mving avec les plages
Zanna
1
@Sparhawk l'OP a écrit très clairement, à partir de la première version de la question, que le problème est l' argument list too longerreur. Il n'est pas nécessaire de clarifier davantage, ce n'est clairement pas une dupe, car nous avons besoin d'une solution de contournement pour traiter ARG_MAX et les réponses dans le doublon proposé ne le font pas.
terdon

Réponses:

25

Une façon consiste à utiliser findavec -execet l' +option. Cela construit une liste d'arguments, mais divise la liste en autant d'appels que nécessaire pour opérer sur tous les fichiers sans dépasser la liste d'arguments maximale. Il convient lorsque tous les arguments seront traités de la même manière. C'est le cas avec rename, mais pas avec mv.

Vous devrez peut-être installer Perl renommer:

sudo apt install rename

Ensuite, vous pouvez utiliser, par exemple:

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

Supprimez -naprès le test pour renommer les fichiers.

Zanna
la source
11

Je vais proposer trois alternatives. Chacun est une simple commande sur une seule ligne, mais je fournirai des variantes pour les cas plus compliqués, principalement au cas où les fichiers à traiter sont mélangés avec d'autres fichiers dans le même répertoire.

mmv

J'utiliserais la commande mmv du package du même nom :

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

Notez que les arguments sont passés sous forme de chaînes, donc l'expansion glob ne se produit pas dans le shell. La commande reçoit exactement deux arguments, puis trouve les fichiers correspondants en interne, sans limites strictes sur le nombre de fichiers. Notez également que la commande ci-dessus suppose que tous les fichiers qui correspondent au premier glob doivent être renommés. Bien sûr, vous êtes libre d'être plus précis:

mmv 'sb_606_HBO_DPM_*' 'sb_606_dpm_#1'

Si vous avez des fichiers en dehors de la plage de numéros demandée dans le même répertoire, vous pourriez être mieux avec la boucle sur les numéros donnés plus bas dans cette réponse. Cependant, vous pouvez également utiliser une séquence d'invocations mmv avec des modèles appropriés:

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

boucle sur les nombres

Si vous voulez éviter d'installer quoi que ce soit, ou devez sélectionner par plage de numéros en évitant les correspondances en dehors de cette plage, et que vous êtes prêt à attendre 74 023 appels de commandes, vous pouvez utiliser une boucle bash simple:

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

Cela fonctionne particulièrement bien ici car il n'y a pas de lacunes dans la séquence. Sinon, vous souhaiterez peut-être vérifier si le fichier source existe réellement.

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

Notez que contrairement à for ((i=89000; i<=163022; ++i))l'expansion d'accolade, il gère les zéros en tête depuis la sortie de certains Bash il y a quelques années. En fait, un changement que j'ai demandé, donc je suis heureux de voir des cas d'utilisation pour cela.

Pour en savoir plus: Brace Expansion dans les pages d'informations de Bash, en particulier la partie sur {x..y[..incr]}.

boucler sur des fichiers

Une autre option serait de boucler sur un glob approprié, au lieu de simplement boucler sur la plage entière en question. Quelque chose comme ça:

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

Encore une fois, il s'agit d'une seule mvinvocation par fichier. Et encore une fois, la boucle est sur une longue liste d'éléments, mais la liste entière n'est pas passée en argument à un sous-processus, mais gérée en interne par bash, donc la limite ne vous causera pas de problèmes.

Pour en savoir plus: Shell Parameter Expansion dans les pages d'informations de Bash, documentant ${parameter/pattern/string}entre autres.

Si vous souhaitez restreindre la plage de numéros à celle que vous avez fournie, vous pouvez ajouter une vérification pour cela:

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

Ici , ${i##pattern}supprime le plus long préfixe correspondant patternde $i. Ce préfixe le plus long est défini comme n'importe quoi, puis un trait de soulignement, puis zéro ou plusieurs zéros. Ce dernier est écrit comme *(0)ce qui est une extension modèle de glob qui dépend de l' extgloboption définie. La suppression des zéros de tête est importante pour traiter le nombre comme base 10 et non base 8. L' +([0-9])argument dans la boucle est un autre glob étendu, correspondant à un ou plusieurs chiffres, juste au cas où vous auriez des fichiers qui commencent de la même manière mais ne se terminent pas par un nombre.

MvG
la source
Merci! Cela a fonctionné comme un rêve: pour i dans {0089000..0163022}; faire mv sb_606_HBO_DPM_ $ i sb_606_dpm_ $ i; fait - j'ai dû ajouter l'extension de nom de fichier pour le faire fonctionner, mais il l'a fait exactement ce que je voulais et je comprends même la syntaxe. Merci @MvG
riche
@rich: Heureux, je pourrais vous aider - vous et j'espère que les futurs visiteurs aussi. N'oubliez pas d' accepter la réponse la plus utile. Vous pouvez toujours changer cette coche à l'avenir si quelque chose de mieux se présente.
MvG
10

Une façon de contourner la ARG_MAXlimite est d'utiliser la fonction intégrée du shell bash 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

mais

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)
tournevis
la source
7
find . -type f -exec bash -c 'echo $1 ${1/HBO_DPM/dpm}' _ {} \;
./sb_606_HBO_DPM_0089000 ./sb_606_dpm_0089000

finddans le répertoire actuel .pour tous les fichiers -type fet renommez le fichier trouvé $1en le remplaçant HBO_DPMpar dmp un par un-exec ... \;

remplacer echopar mvpour effectuer le changement de nom.

αғsнιη
la source
6

Vous pouvez écrire un petit script python, quelque chose comme:

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

Enregistrez-le en tant que fichier texte comme rename.pydans le dossier dans lequel se trouvent les fichiers, puis avec le terminal dans ce dossier:

python rename.py
Crâne de pierre
la source
6

Vous pouvez le faire fichier par fichier (cela peut prendre un certain temps) avec

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

Comme le Perl renameutilisé dans d'autres réponses, rename.ula également une option -nou --no-actpour les tests.

muclux
la source
J'ai supprimé votre commentaire sur la réponse de Zanna, veuillez modifier la réponse de Zanna ou laisser un commentaire.
fosslinux
@ubashu qui n'était pas un commentaire sur ma réponse - il faisait référence au -ndrapeau que j'ai utilisé pour les tests et suggérant qu'il pouvait également être utilisé rename.ul.
Zanna
3

Je vois que personne n'a invité mon meilleur ami sedà la fête :). La forboucle suivante atteindra votre objectif:

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

Il existe de nombreux outils pour un tel travail, sélectionnez celui qui vous convient le mieux. Celui-ci est simple et facilement modifié pour convenir à cela ou à d'autres fins ...

andrew.46
la source
Accordé, pas très pertinent dans ce cas spécifique, mais cela échouera si l'un des noms de fichiers contient des retours à la ligne. Je mentionne cela car la plupart (toutes?) Les autres réponses sont robustes et peuvent traiter des noms de fichiers arbitraires, ou ne fonctionnent que sur le schéma de dénomination des fichiers de l'OP.
terdon
... sauts de ligne, espaces, caractères génériques, ... dont certains peuvent être évités en citant $idans la substitution de commande, mais pas un moyen facile de gérer un saut de ligne en fin de fichier.
muru
3

Puisque nous proposons des options, voici une approche Perl. cddans le répertoire cible et exécutez:

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

Explication

  • perl -e: lance le script donné par -e.
  • foreach(glob){}: exécuter ce qui se trouve { }sur chaque résultat du glob.
  • glob("sb_*"): retourne une liste de tous les fichiers et répertoires du répertoire courant dont les noms correspondent au glob du shell sb*.
  • rename $_, s/_HBO_DPM_/_dpm_/r: perl magic. $_est une variable spéciale qui contient chaque élément sur lequel nous itérons (dans le foreach). Alors ici, ce sera chaque fichier trouvé. s/_HBO_DPM_/_dpm_/remplace la première occurrence de _HBO_DPM_avec _dpm_. Il s'exécute $_par défaut, il s'exécutera donc sur chaque nom de fichier. Les /rmoyens « appliquent ce remplacement à une copie de la chaîne cible (le nom du fichier) et retourner la chaîne modifiée. renameEst -ce que ce que vous attendez: il Renomme les fichiers Donc tout cela va renommer le nom du fichier en cours (. $_À lui - même) avec _HBO_DPM_remplacé par _dpm_.

Vous pouvez écrire la même chose qu'un script développé (et plus lisible):

#! /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
la source
1

Selon le type de changement de nom que vous envisagez, utilisez vidir avec l' de plusieurs lignes peut être satisfaisante.
Dans votre cas particulier, vous pouvez sélectionner toutes les lignes dans votre éditeur de texte et supprimer la partie _ " HBO" des noms de fichiers en quelques touches.

kraymer
la source
ouais, vi a gloable trouver et remplacer.
Jasen
2
Pouvez-vous s'il vous plaît étendre votre réponse et donner un exemple sur la façon d'atteindre l'objectif d'OP vidir?
dessert