web-dev-qa-db-fra.com

Renommer un grand nombre de fichiers image avec bash

J'ai besoin de renommer env. 70 000 fichiers. Par exemple: De sb_606_HBO_DPM_0089000 à sb_606_dpm_0089000 etc.

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 de manière séquentielle (une séquence d'images). Les chiffres doivent rester inchangés.

Lorsque j'essaie de le faire, cela me fait comprendre que la "liste d'arguments est trop longue".

Modifier:

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, alors je pensais que la même syntaxe pourrait fonctionner pour renommer les fichiers ...). Je pense 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
16
rich

Une solution consiste à utiliser find avec -exec et l'option +. Ceci construit une liste d'arguments, mais divise la liste en autant d'appels que nécessaire pour fonctionner sur tous les fichiers sans dépasser la liste d'arguments maximale. Il convient que tous les arguments soient traités de la même manière. C'est le cas avec rename, mais pas avec mv.

Vous devrez peut-être installer Perl rename:

Sudo apt install rename

Ensuite, vous pouvez utiliser, par exemple:

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

Supprimez -n après le test pour renommer les fichiers.

25
Zanna

Je vais suggérer trois alternatives. Chacune est une simple commande d'une seule ligne, mais je proposerai des variantes pour les cas plus complexes, principalement dans le cas où les fichiers à traiter sont mélangés à d'autres fichiers du même répertoire.

mmv

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

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

Notez que les arguments sont passés en tant que chaînes, de sorte que l'expansion globale ne se produit pas dans le shell. La commande reçoit exactement deux arguments, puis trouve les fichiers correspondants en interne, sans limitation stricte du nombre de fichiers. Notez également que la commande ci-dessus suppose que tous les fichiers correspondant 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, il serait peut-être préférable de passer en boucle les numéros indiqués plus bas dans cette réponse. Cependant, vous pouvez également utiliser une séquence d'appels 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 chiffres

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

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 pouvez 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’extension d’accolade gère les zéros non significatifs depuis la sortie de certaines versions de 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.

Lectures supplémentaires: Expansion de l'accolade dans les pages d'informations sur Bash, en particulier la partie concernant {x..y[..incr]}.

boucle sur les fichiers

Une autre option serait de passer en boucle sur un glob adéquat, au lieu de simplement passer en boucle 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 invocation mv par fichier. Et encore une fois, la boucle couvre une longue liste d’éléments, mais toute la liste n’est pas transmise en tant qu’argument à un sous-processus, mais gérée en interne par bash, de sorte que la limite ne vous causera pas de problèmes.

Lectures supplémentaires: Expansion des paramètres du shell dans les pages d’informations Bash, documentant ${parameter/pattern/string}, entre autres.

Si vous souhaitez limiter la plage de numéros à celle que vous avez fournie, vous pouvez cocher cette case:

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 à pattern de $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 sous la forme *(0), qui est un modèle global étendu qui dépend de l'option extglob en cours de définition. Il est important de supprimer les zéros de gauche pour traiter le nombre en base 10 et non en base 8. Le +([0-9]) dans l'argument de 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 façon mais ne se terminent pas par là. un numéro.

11
MvG

Une façon de contourner la limite ARG_MAX consiste à utiliser le nom intégré printf du shell bash:

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

find dans le répertoire en cours . pour tous les fichiers -type f et renommez le fichier trouvé $1 en remplaçant HBO_DPM par dmpn par un -exec ... \;

remplacez echo par mv pour renommer.

7
αғsнιη

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.ul a aussi une option -n ou --no-act à des fins de test.

6
muclux

Vous pouvez écrire un petit script python, du type:

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

Enregistrez-le sous forme de fichier texte sous le nom rename.py dans le dossier dans lequel se trouvent les fichiers, puis avec le terminal dans ce dossier, ouvrez:

python rename.py
6
Stone Skull

Puisque nous donnons des options, voici une approche Perl. cd dans 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écute tout ce qui est dans le { } sur chaque résultat du glob.
  • glob("sb_*"): renvoie la liste de tous les fichiers et répertoires du répertoire en cours dont les noms correspondent au shell glob 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). Donc ici, ce sera chaque fichier trouvé. s/_HBO_DPM_/_dpm_/ remplace la première occurrence de _HBO_DPM_ par _dpm_. Il fonctionne sur $_ par défaut, il sera donc exécuté sur chaque nom de fichier. Le /r signifie "applique ce remplacement à une copie de la chaîne cible (le nom du fichier) et renvoie la chaîne modifiée. rename fait ce que vous attendez: il renomme les fichiers. Ainsi, le nom complet sera renommé ($_). à lui-même avec _HBO_DPM_ remplacé par _dpm_.

Vous pourriez é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;
}
3
terdon

Je vois que personne n'a invité mon meilleur ami sed à la fête :). La boucle for suivante remplira 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, choisissez celui qui vous convient le mieux. Celui-ci est simple et facilement modifiable pour répondre à cela ou à d'autres fins ...

3
andrew.46

En fonction du type de changement de nom envisagé, vous pouvez utiliser vidir avec une édition sur plusieurs lignes.
Dans votre cas particulier, vous pouvez sélectionner toutes les lignes de votre éditeur de texte et supprimer la partie _ " HBO" des noms de fichiers en quelques frappes de touche.

1
kraymer