web-dev-qa-db-fra.com

Comment renommer plusieurs fichiers à l'aide de la recherche

Je veux renommer plusieurs fichiers (file1 ... filen en file1_renamed ... filen_renamed) en utilisant la commande find:

find . -type f -name 'file*' -exec mv filename='{}' $(basename $filename)_renamed ';'

Mais obtenir cette erreur:

mv: cannot stat ‘filename=./file1’: No such file or directory

Cela ne fonctionne pas car le nom de fichier n'est pas interprété comme une variable Shell.

41
user164014

Voici une solution directe à votre approche:

find . -type f -name 'file*' -exec sh -c 'x="{}"; mv "$x" "${x}_renamed"' \;

Cependant, cela coûte très cher si vous avez beaucoup de fichiers correspondants, car vous démarrez un nouveau Shell (qui exécute un mv) pour chaque correspondance. Et si vous avez des personnages amusants dans n'importe quel nom de fichier, cela explosera. Voici une approche plus efficace et plus sûre:

find . -type f -name 'file*' -print0 | xargs --null -I{} mv {} {}_renamed

Il a également l'avantage de travailler avec des fichiers aux noms étranges. Si find le prend en charge, cela peut être réduit à

find . -type f -name 'file*' -exec mv {} {}_renamed \;

La version xargs est utile lorsque vous n'utilisez pas {}, un péché

find .... -print0 | xargs --null rm

Ici, rm est appelé une fois (ou avec plusieurs fichiers plusieurs fois), mais pas pour chaque fichier.

J'ai supprimé le basename dans votre question, car il est probablement faux: vous déplaceriez foo/bar/file8 à file8_renamed, ne pas foo/bar/file8_renamed.

Modifications (comme suggéré dans les commentaires):

  • Ajouté raccourci find sans xargs
  • Autocollant de sécurité ajouté
49
Thomas Erker

Après avoir essayé la première réponse et en jouant un peu avec, j'ai trouvé que cela peut être fait un peu plus court et moins complexe en utilisant -execdir:

find . -type f -name 'file*' -execdir mv {} {}_renamed ';'

On dirait qu'il devrait également faire exactement ce dont vous avez besoin.

17
Matijs

Une autre approche consiste à utiliser une boucle while read Sur la sortie find. Cela permet d'accéder à chaque nom de fichier en tant que variable pouvant être manipulée sans avoir à se soucier des coûts supplémentaires/problèmes de sécurité potentiels liés à la génération d'un processus sh -c Séparé en utilisant l'option -exec De find .

find . -type f -name 'file*' |
    while IFS= read file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done

Et si le shell utilisé prend en charge l'option -d Pour spécifier un délimiteur read, vous pouvez prendre en charge les fichiers aux noms étranges (par exemple avec une nouvelle ligne) en utilisant ce qui suit:

find . -type f -name 'file*' -print0 |
    while IFS= read -d '' file_name; do
        mv "$file_name" "${file_name##*\/}_renamed"
    done
8
John B

Je veux développer la première réponse et noter que cela ne fonctionnera pas pour s'ajouter au nom de fichier depuis le ./ le préfixe du chemin est présent dans l'argument du nom de fichier.

En modifiant la réponse de Thomas Erker, je trouve celle-ci une approche plus générique

find . -name PATTERN -printf "%f\0" | xargs --null -I{} mv {} "prefix {} suffix"

options de xargs:

--null Indique que chaque argument passé par stdin se termine par un caractère nul (\0). De cette façon, le nom de fichier peut contenir des espaces, sinon chaque mot sera traité comme un paramètre différent pour la commande mv.

-I replace-str Chaque occurrence de replace-str sera remplacé par l'argument lu dans stdin. Donc, vous pouvez le changer pour une autre chaîne si vous en avez besoin.

4
Sdlion

J'ai pu faire quelque chose de similaire avec les for, find et mv.

for i in $(find . -name 'config.yml'); do mv $i $i.bak; done

Cela trouve tous les config.yml fichiers et les renomme config.yml.bak

2
Varun Risbud