Utilisation de sed pour renommer en masse des fichiers

87

Objectif

Modifiez ces noms de fichiers:

  • F00001-0708-RG-biasliuyda
  • F00001-0708-CS-akgdlaul
  • F00001-0708-VF-hioulgigl

à ces noms de fichiers:

  • F0001-0708-RG-biasliuyda
  • F0001-0708-CS-akgdlaul
  • F0001-0708-VF-hioulgigl

Code Shell

Tester:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/'

Pour effectuer:

ls F00001-0708-*|sed 's/\(.\).\(.*\)/mv & \1\2/' | sh

Ma question

Je ne comprends pas le code sed. Je comprends ce que la commande de substitution

$ sed 's/something/mv'

veux dire. Et je comprends un peu les expressions régulières. Mais je ne comprends pas ce qui se passe ici:

\(.\).\(.*\)

ou ici:

& \1\2/

Le premier, pour moi, ressemble simplement à cela: "un seul caractère, suivi d'un seul caractère, suivi de toute séquence de longueur d'un seul caractère" - mais il y a sûrement plus que cela. En ce qui concerne la dernière partie:

& \1\2/

Je n'ai aucune idée.

Daniel Underwood
la source

Réponses:

149

Tout d'abord, je dois dire que le moyen le plus simple de le faire est d'utiliser les commandes prename ou renommer.

Sur Ubuntu, OSX (package Homebrew, package renameMacPorts p5-file-rename) ou d'autres systèmes avec un renommage perl (prename):

rename s/0000/000/ F0000*

ou sur les systèmes renommés de util-linux-ng, tels que RHEL:

rename 0000 000 F0000*

C'est beaucoup plus compréhensible que la commande sed équivalente.

Mais pour comprendre la commande sed, la page de manuel sed est utile. Si vous exécutez man sed et recherchez & (en utilisant la commande / pour rechercher), vous trouverez qu'il s'agit d'un caractère spécial dans s / foo / bar / remplacements.

  s/regexp/replacement/
         Attempt  to match regexp against the pattern space.  If success‐
         ful,  replace  that  portion  matched  with  replacement.    The
         replacement may contain the special character & to refer to that
         portion of the pattern space  which  matched,  and  the  special
         escapes  \1  through  \9  to refer to the corresponding matching
         sub-expressions in the regexp.

Par conséquent, \(.\)correspond au premier caractère, qui peut être référencé par \1. Puis .correspond au caractère suivant, qui est toujours 0. Puis \(.*\)correspond au reste du nom de fichier, qui peut être référencé par \2.

La chaîne de remplacement met tout cela ensemble en utilisant &(le nom de fichier d'origine) et \1\2qui est chaque partie du nom de fichier à l'exception du 2ème caractère, qui était un 0.

C'est une façon assez cryptique de faire cela, à mon humble avis. Si pour une raison quelconque la commande renommer n'était pas disponible et que vous vouliez utiliser sed pour faire le changement de nom (ou peut-être que vous faisiez quelque chose de trop complexe pour renommer?), Être plus explicite dans votre regex la rendrait beaucoup plus lisible. Peut-être quelque chose comme:

ls F00001-0708-*|sed 's/F0000\(.*\)/mv & F000\1/' | sh

Être capable de voir ce qui change réellement dans le s / search / replacement / le rend beaucoup plus lisible. De plus, il ne continuera pas à aspirer les caractères de votre nom de fichier si vous l'exécutez accidentellement deux fois ou quelque chose du genre.

Edward Anderson
la source
1
sur mon serveur RHEL, la syntaxe de changement de nom serait "renommer 0000 000 F0000 *"
David LeBauer
1
Il est fort probable que ce renamesoit lui-même un "renommé" lien . ie renamea été "renommé" de prename.. par exemple, dans Ubuntu: readlink -f $(which rename)sorties /usr/bin/prename... Le renamementionné par David est un programme entièrement différent.
Peter.O
1
Bon point, Peter. J'ai mis à jour la réponse pour traiter les deux utilitaires de changement de nom.
Edward Anderson
3
Pour déboguer cela, supprimez le tuyau dans sh à la fin. Les commandes se répercuteront sur l'écran.
Ben Mathews
1
Êtes-vous sûr que c'est un bon conseil à donner pour acheminer des données aléatoires sh? ceci est potentiellement dangereux car du code arbitraire peut être exécuté (vous traitez les données comme du code).
gniourf_gniourf
44

vous avez eu votre explication sed, maintenant vous pouvez utiliser uniquement le shell, pas besoin de commandes externes

for file in F0000*
do
    echo mv "$file" "${file/#F0000/F000}"
    # ${file/#F0000/F000} means replace the pattern that starts at beginning of string
done
ghostdog74
la source
1
Bien mais vous ne pouvez pas faire de références avec des parenthèses.
Leonidas Tsampros
26

J'ai écrit un petit article avec des exemples sur le changement de nom par lots en utilisant sed quelques années:

http://www.guyrutenberg.com/2009/01/12/batch-renaming-using-sed/

Par exemple:

for i in *; do
  mv "$i" "`echo $i | sed "s/regex/replace_text/"`";
done

Si l'expression régulière contient des groupes (par exemple \(subregex\), vous pouvez les utiliser dans le texte de remplacement comme \1\, \2etc.

Gars
la source
Notez que les réponses aux liens uniquement sont déconseillées (les liens ont tendance à devenir obsolètes avec le temps). Veuillez envisager de modifier votre réponse et d'ajouter un synopsis ici.
kleopatra
pas si efficace, mais fait le travail pour quelques centaines de fichiers. J'ai voté pour.
Varun Chandak le
21

Le moyen le plus simple serait:

for i in F00001*; do mv "$i" "${i/F00001/F0001}"; done

ou, portatif,

for i in F00001*; do mv "$i" "F0001${i#F00001}"; done

Cela remplace le F00001préfixe dans les noms de fichiers par F0001. crédits à mahesh ici: http://www.debian-administration.org/articles/150

Mike
la source
3
Vous devez correctement citer les interpolations variables; mv "$i" "${i/F00001/F0001}". Mais +1
tripleee le
7

La sedcommande

s/\(.\).\(.*\)/mv & \1\2/

signifie remplacer:

\(.\).\(.*\)

avec:

mv & \1\2

comme une sedcommande ordinaire . Cependant, les parenthèses &et les \nmarqueurs le changent un peu.

La chaîne de recherche correspond (et se souvient comme motif 1) du caractère unique au début, suivi d'un seul caractère, suivi du reste de la chaîne (mémorisé comme motif 2).

Dans la chaîne de remplacement, vous pouvez faire référence à ces modèles correspondants pour les utiliser dans le cadre du remplacement. Vous pouvez également faire référence à toute la partie correspondante comme &.

Donc, ce que sedfait cette commande est de créer une mvcommande basée sur le fichier d'origine (pour la source) et les caractères 1 et 3 en avant, supprimant effectivement le caractère 2 (pour la destination). Il vous donnera une série de lignes selon le format suivant:

mv F00001-0708-RG-biasliuyda F0001-0708-RG-biasliuyda
mv abcdef acdef

etc.

paxdiablo
la source
1
C'était une bonne explication, mais il pourrait être utile d'indiquer comment vous utilisez la commande sed avec d'autres commandes pour renommer les fichiers. Par exemple:ls | sed "s/\(.\).\(.*\)/mv & \1\2/" | bash
jcarballo
@jcarballo: il est dangereux d'analyser ls, tuyau à travers sedet conduit ensuite à travers une coquille! il est soumis à l'exécution de code arbitraire avec des noms de fichiers falsifiés. Le problème est que les données doivent être traitées comme des données, et ici, elles sont généralement sérialisées en code sans aucune précaution. Je souhaite que paxdiablo puisse supprimer cette réponse car elle ne montre vraiment pas de bonnes pratiques. (Je suis tombé sur cette question parce qu'un débutant a lancé au hasard | shune commande qui n'a pas fonctionné et après avoir vu cette question et les réponses ont pensé que cela fonctionnerait mieux - je suis horrifié!) :).
gniourf_gniourf
3

Le truc de backslash-paren signifie, "tout en faisant correspondre le modèle, gardez le truc qui correspond ici." Plus tard, du côté du texte de remplacement, vous pouvez récupérer ces fragments mémorisés avec "\ 1" (premier bloc entre parenthèses), "\ 2" (deuxième bloc), et ainsi de suite.

Pointu
la source
1

Si tout ce que vous faites vraiment est de supprimer le deuxième caractère, quel qu'il soit, vous pouvez le faire:

s/.//2

mais votre commande construit une mvcommande et la transmet au shell pour exécution.

Ce n'est pas plus lisible que votre version:

find -type f | sed -n 'h;s/.//4;x;s/^/mv /;G;s/\n/ /g;p' | sh

Le quatrième caractère est supprimé car il findajoute «./» à chaque nom de fichier.

Suspendu jusqu'à nouvel ordre.
la source
Je souhaite que vous puissiez supprimer cette réponse. Alors que c'était peut-être bien dans le cas très spécifique de l'OP, il y a beaucoup de gens qui voient des réponses comme celle-ci et ne les comprennent pas, et qui | shsuivent au hasard une commande qui ne fonctionne pas, dans l'espoir que cela fonctionnera mieux. C'est horrible! (et en plus, ce n'est pas une bonne pratique). J'espère que vous comprendrez!
gniourf_gniourf
0

Les parenthèses capturent des chaînes particulières à utiliser par les nombres avec une barre oblique inverse.

Ewan Todd
la source
0
 ls F00001-0708-*|sed 's|^F0000\(.*\)|mv & F000\1|' | bash
ghostdog74
la source
Horrible! sujet à l'exécution de code arbitraire (peut-être pas dans le contexte spécifique de la question, mais il y a beaucoup de gens qui voient des réponses comme celle-ci et essaient de taper au hasard quelque chose qui lui ressemble, et c'est effrayant dangereux!). J'aimerais que vous puissiez supprimer cette réponse (en plus, vous en avez une autre bonne ici, que j'ai votée pour).
gniourf_gniourf
0

Voici ce que je ferais:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done

Ensuite, si cela semble correct, ajoutez | shà la fin. Donc:

for file in *.[Jj][Pp][Gg] ;do 
    echo mv -vi \"$file\" `jhead $file|
                           grep Date|
                           cut -b 16-|
                           sed -e 's/:/-/g' -e 's/ /_/g' -e 's/$/.jpg/g'` ;
done | sh
Chris Po
la source
0

Utilisation du renommage perl ( indispensable dans la boîte à outils):

rename -n 's/0000/000/' F0000*

Retirez le -ncommutateur lorsque la sortie semble bonne pour le renommer pour de vrai.

Attention Il existe d'autres outils portant le même nom qui peuvent ou non être en mesure de le faire, alors soyez prudent.

La commande renommer qui fait partie de la util-linux package ne le fera pas.

Si vous exécutez la commande suivante ( GNU)

$ rename

et tu vois perlexpr , alors cela semble être le bon outil.

Sinon, pour en faire la valeur par défaut (généralement déjà le cas) sur Debianet dérivé comme Ubuntu:

$ sudo apt install rename
$ sudo update-alternatives --set rename /usr/bin/file-rename

Pour archlinux:

pacman -S perl-rename

Pour les distributions de la famille RedHat:

yum install prename

Le package 'prename' se trouve dans le référentiel EPEL .


Pour Gentoo:

emerge dev-perl/rename

Pour * BSD:

pkg install gprename

ou p5-File-Rename


Pour les utilisateurs Mac:

brew install rename

Si vous n'avez pas cette commande avec une autre distribution, recherchez votre gestionnaire de paquets pour l'installer ou faites-le manuellement :

cpan -i File::Rename

L'ancienne version autonome peut être trouvée ici


homme renommer


Cet outil a été à l'origine écrit par Larry Wall, le père de Perl.

Gilles Quenot
la source
-1
for i in *; do mv $i $(echo $i|sed 's/AAA/BBB/'); done
user3164360
la source
4
Bienvenue à SO. Pensez à ajouter une explication de votre code. Cela aidera les autres utilisateurs à le comprendre.
Digvijay S
Cette réponse est bonne, mais c'est une réponse presque en double d'une réponse hautement votée ci-dessus.
Eric Leschinski