Comment changer l'extension de plusieurs fichiers de manière récursive à partir de la ligne de commande?

73

J'ai beaucoup de fichiers avec l' .abcextension et je veux les changer en .edefg
Comment faire cela en ligne de commande?

J'ai un dossier racine avec de nombreux sous-dossiers, la solution doit donc fonctionner de manière récursive.

Tommyk
la source
Voir le site askubuntu.com/questions/10607/…
belacqua le

Réponses:

85

Un moyen portable (qui fonctionnera sur tout système compatible POSIX):

find /the/path -depth -name "*.abc" -exec sh -c 'mv "$1" "${1%.abc}.edefg"' _ {} \;

Dans bash4, vous pouvez utiliser globstar pour obtenir des globs récursifs (**):

shopt -s globstar
for file in /the/path/**/*.abc; do
  mv "$file" "${file%.abc}.edefg"
done

La renamecommande (perl) d’Ubuntu permet de renommer des fichiers à l’aide de la syntaxe d’expression régulière Perl, que vous pouvez combiner avec globstar ou find:

# Using globstar
shopt -s globstar
files=(/the/path/**/*.abc)  

# Best to process the files in chunks to avoid exceeding the maximum argument 
# length. 100 at a time is probably good enough. 
# See http://mywiki.wooledge.org/BashFAQ/095
for ((i = 0; i < ${#files[@]}; i += 100)); do
  rename 's/\.abc$/.edefg/' "${files[@]:i:100}"
done

# Using find:
find /the/path -depth -name "*.abc" -exec rename 's/\.abc$/.edefg/' {} +

Voir aussi http://mywiki.wooledge.org/BashFAQ/030

geirha
la source
le premier ajoute simplement la nouvelle extension à l'ancienne, il ne remplace pas ...
user2757729
3
@ user2757729, il le remplace. "${1%.abc}"se développe à la valeur de $1sauf sans .abcà la fin. Voir la FAQ 100 pour en savoir plus sur les manipulations de chaînes de shell.
geirha
J'ai exécuté ceci sur une structure de fichier de ~ 70k fichiers ... au moins sur mon shell, cela ne l'a certainement pas remplacé.
user2757729
1
@ user2757729 Vous avez probablement quitté le "abc" dans${1%.abc}...
kvnn
1
Si quelqu'un d'autre se demande ce que le trait de soulignement fait dans la première commande, cela est nécessaire car, avec sh -c le premier argument, il est attribué à $ 0 et tous les arguments restants sont attribués aux paramètres de position . Donc, l' _argument est factice, et '{}' est le premier argument de position sh -c.
Hellobenallan le
48

Cela fera la tâche requise si tous les fichiers sont dans le même dossier

rename 's/.abc$/.edefg/' *.abc

Pour renommer les fichiers de manière récursive, utilisez ceci:

find /path/to/root/folder -type f -name '*.abc' -print0 | xargs -0 rename 's/.abc$/.edefg/'
Chakra
la source
8
Ou rename 's/.abc$/.edefg/' /path/to/root/folder/**/*.abcdans une version moderne de Bash.
Adam Byrtek le
Merci beaucoup, Adam, de m'avoir indiqué comment utiliser * .abc de manière récursive dans les dossiers!
Rafał Cieślak le
Excellent conseil, merci! Où puis-je trouver plus de documentation sur le petit morceau de syntaxe de regex? Comme quoi sau début et quelles autres options puis-je utiliser là-bas? Merci !
Anto
@AdamByrtek J'ai juste échoué avec votre suggestion sur Utopic. ('no de tels fichiers' ou autres.)
Jonathan Y.
14

Un problème avec les renommements récursifs est que quelle que soit la méthode utilisée pour localiser les fichiers, elle transmet le chemin complet rename, et pas seulement le nom du fichier. Cela rend difficile de renommer des dossiers complexes dans des dossiers imbriqués.

J'utilise findl' -execdiraction pour résoudre ce problème. Si vous utilisez -execdirplutôt que de -exec, la commande spécifiée est exécutée à partir du sous-répertoire contenant le fichier correspondant. Ainsi, au lieu de passer par tout le chemin rename, il ne fait que passer ./filename. Cela facilite beaucoup l'écriture de la regex.

find /the/path -type f \
               -name '*.abc' \
               -execdir rename 's/\.\/(.+)\.abc$/version1_$1.abc/' '{}' \;

En détail:

  • -type f signifie seulement chercher des fichiers, pas des répertoires
  • -name '*.abc' signifie que ne correspondent qu'aux noms de fichiers se terminant par .abc
  • '{}'est l'espace réservé qui marque l'endroit où -execdirsera inséré le chemin trouvé. Les guillemets simples sont obligatoires pour lui permettre de gérer les noms de fichiers avec des espaces et des caractères shell.
  • Les barres obliques inverses après -typeet -namesont le caractère de continuation de ligne bash. Je les utilise pour rendre cet exemple plus lisible, mais ils ne sont pas nécessaires si vous placez votre commande sur une seule ligne.
  • Toutefois, la barre oblique inverse à la fin de la -execdirligne est requise. Il est là pour échapper au point-virgule, qui termine la commande exécutée par -execdir. Amusement!

Explication de la regex:

  • s/ début de la regex
  • \.\/correspond au premier ./ que -execdir transmet. Utilisez \ pour sortir du fichier. et / metacharacters (note: cette partie varie en fonction de votre version de find. Voir le commentaire de l'utilisateur @apollo)
  • (.+)correspondre au nom de fichier. Les parenthèses capturent la correspondance pour une utilisation ultérieure
  • \.abc échapper au point, correspondre à l'abc
  • $ ancrer l'allumette à la fin de la chaîne

  • / marque la fin de la partie "match" de l'expression régulière, et le début de la partie "remplacer"

  • version1_ ajouter ce texte à chaque nom de fichier

  • $1référence le nom de fichier existant, car nous l'avons capturé avec des parenthèses. Si vous utilisez plusieurs jeux de parenthèses dans la partie "correspondance", vous pouvez vous y référer ici en utilisant $ 2, 3 $, etc.
  • .abcle nouveau nom de fichier se terminera par .abc. Pas besoin d'échapper au métacaractère de point ici dans la section "remplacer"
  • / fin de la regex

Avant

tree --charset=ascii

|-- a_file.abc
|-- Another.abc
|-- Not_this.def
`-- dir1
    `-- nested_file.abc

Après

tree --charset=ascii

|-- version1_a_file.abc
|-- version1_Another.abc
|-- Not_this.def
`-- dir1
    `-- version1_nested_file.abc

Astuce: renamel'option 's -n est utile. Il effectue un essai et vous indique les noms qu'il va changer, mais ne fait aucun changement.

Christian Long
la source
Merci pour l'explication des détails, mais étrangement, lorsque j'essaie, le passage --execdirn'est pas passé au premier plan ./pour que la \.\/partie dans la regex puisse être omise. Mai, c'est parce que je suis sur OSX pas ubuntu
apollo
5

Une autre façon portable:

find /the/path -depth -type f -name "*.abc" -exec sh -c 'mv -- "$1" "$(dirname "$1")/$(basename "$1" .abc).edefg"' _ '{}' \;
un Neutrino
la source
4
Bienvenue sur Ask Ubuntu! Bien que cette réponse soit précieuse, je vous recommande de la développer (en la modifiant) pour expliquer comment et pourquoi cette commande fonctionne.
Eliah Kagan
0

C'est ce que j'ai fait et travaillé exactement comme je le voulais. J'ai utilisé la mvcommande. J'avais plusieurs .3gpfichiers et je voulais tous les renommer.mp4

Voici un bref résumé pour cela:

for i in *.3gp; do mv -- "$i" "ren-$i.mp4"; done

Qui scanne simplement à travers le répertoire courant, ramasse tous les fichiers .3gp, puis les renomme (en utilisant le mv) en ren-name_of_file.mp4

KhoPhi
la source
Ce renommera file.3gpà file.3gp.mp4, qui pourrait ne pas être ce que la plupart des gens veulent.
Muru
@muru mais le principe existe toujours. Il suffit de sélectionner une extension, puis de spécifier l’extension de destination pour les renommer. Le .3gpou .mp4ici étaient juste à des fins d'illustration.
KhoPhi
La syntaxe est préférable, simple et facile à comprendre. Cependant, je voudrais utiliser basename "$i" .mp4l'extension précédente au lieu de "ren- $ i.mp4".
TaoPR
Vrai. Bon point.
KhoPhi
0

J'ai trouvé un moyen facile d'y parvenir. Pour changer les extensions de nombreux fichiers de jpg en pdf, utilisez:

for file in /path/to; do mv $file $(basename -s jpg $file)pdf ; done
Paisible
la source
0

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

mmv ';*.abc' '#1#2.edefg'

Les ;correspondances zéro ou plus */et correspond à #1dans le remplacement. Le *correspond à #2. La version non récursive serait

mmv '*.abc' '#1.edefg'
MvG
la source
0

Renommez les fichiers et les répertoires avec find -execdir | rename

Si vous voulez renommer les fichiers et les répertoires, pas simplement avec un suffixe, c'est un bon modèle:

PATH="$(echo "$PATH" | sed -E 's/(^|:)[^\/][^:]*//g')" \
  find . -depth -execdir rename 's/findme/replaceme/' '{}' \;

L' -execdiroption awesome fait un cddans le répertoire avant d'exécuter la renamecommande, contrairement à -exec.

-depth Assurez-vous que le changement de nom a lieu en premier lieu sur les enfants, puis sur les parents, afin d'éviter d'éventuels problèmes avec les répertoires parents manquants.

-execdir est obligatoire car le changement de nom ne fonctionne pas correctement avec les chemins d'entrée autres que les noms de base. Par exemple, les échecs suivants ont échoué:

rename 's/findme/replaceme/g' acc/acc

Le PATHpiratage est nécessaire car -execdirprésente un inconvénient très gênant: il findest extrêmement avisé et refuse de faire quoi que ce soit avec -execdirsi vous avez des chemins relatifs dans votre PATHvariable d’environnement, par exemple ./node_modules/.bin, en échouant avec:

find: le chemin relatif './node_modules/.bin' est inclus dans la variable d'environnement PATH, qui n'est pas sécurisée en combinaison avec l'action -execdir de find. Veuillez supprimer cette entrée de $ PATH

Voir aussi: Pourquoi utiliser l'action '-execdir' n'est pas sécurisé pour le répertoire qui se trouve dans le PATH?

-execdirest une extension de recherche GNU à POSIX . renameest basé sur Perl et provient du renamepackage. Testé sous Ubuntu 18.10.

Renommer la solution de contournement de lookahead

Si vos chemins d'entrée ne proviennent pas findou si vous en avez assez de la gêne liée au chemin relatif, nous pouvons utiliser un préfixe Perl pour renommer en toute sécurité les répertoires comme suit:

git ls-files | sort -r | xargs rename 's/findme(?!.*\/)\/?$/replaceme/g' '{}'

Je n'ai pas trouvé d'analogue pratique pour -execdiravec xargs: https://superuser.com/questions/893890/xargs-change-working-directory-to-file-path-before-executing/915686

Il sort -rest nécessaire de s'assurer que les fichiers viennent après leurs répertoires respectifs, car les chemins les plus longs viennent après les plus courts avec le même préfixe.

Ciro Santilli 改造 中心 六四 事件
la source