Comment effectuer une action sur tous les fichiers avec une extension spécifique dans les sous-dossiers de manière élégante?


18

Mon meilleur pari actuel est:

for i in $(find . -name *.jpg); do echo $i; done

Problème: ne gère pas les espaces dans les noms de fichiers.

Remarque: j'aimerais également une manière graphique de faire cela, comme la commande "tree".


Vous ne pouvez pas simplement mettre des guillemets $i? Je fais ça et ça marche bien. Y a-t-il un problème avec cette approche?
Umar Farooq Khawaja

Réponses:


26

La voie canonique est de faire

find . -name '*.jpg' -exec echo {} \;

(remplacez \;par +pour transmettre plusieurs fichiers echoà la fois)

ou (spécifique à GNU, bien que certains BSD l'aient également):

find . -name '*.jpg' -print0 | xargs -r0 echo

zsh:

for i (**/*.jpg(D)) echo $i

2
Si vous utilisez xargs -0 , vous devez le faire correspondre avec find -0
itsbruce

1
@ MichaelKjörling, aucune citation {} ne fait aucune différence. {} est développé par find, pas par le shell. Une amélioration serait de ne pas utiliser echoce qui étend les séquences d'échappement comme \b(au moins les conformes Unix echo).
Stéphane Chazelas

1
@sch Êtes-vous sûr? La findpage de manuel s'affiche find . -type f -exec file '{}' \;dans la EXAMPLESsection. Peut-être que certaines coquilles traiteront spécialement les accolades.
QuasarDonkey

1
Les citations ne nuisent pas mais ne font aucune différence. Tous '{}', \{\}, {}, "{}", '{'}sont complétés par le shell (quelle que soit Bourne shell-like) à un argument de constater que les deux personnages est « { » et « } ». Remplacez "find" par "echo find", ou printf '<%s>\n' findsi vous souhaitez revérifier.
Stéphane Chazelas


2

De meilleures réponses ont déjà été données.

Mais notez que les espaces ne sont pas le seul problème dans le code que vous avez donné. les caractères de tabulation, de nouvelle ligne et de caractère générique sont également un problème.

Avec

IFS='
'
set -f
for i in $(find . -name '*.jpg'); do echo "$i"; done

Ensuite, seuls les caractères de nouvelle ligne posent problème.


1

Ce que vous avez mentionné est l'un des problèmes fondamentaux auxquels les gens sont confrontés lorsqu'ils essaient de lire les noms de fichiers. Parfois, les personnes ayant des connaissances limitées et ayant une conception erronée de la structure des fichiers et des dossiers ont tendance à oublier que " Sous UNIX, tout est un fichier ". Ils ne comprennent donc pas qu'ils doivent également gérer les espaces, car le nom de fichier peut être composé de 2 mots ou plus avec des espaces.

Solution: l' une des façons les plus connues de procéder consiste à effectuer une lecture claire .

Nous allons ici lire tous les fichiers présents et les conserver dans une variable, la prochaine fois lors du traitement souhaité, nous devrons simplement garder cette variable entre guillemets qui préservera les noms de fichiers avec des espaces. C'est l'un des moyens de base de le faire, cependant, les autres réponses fournies par les autres personnes ici fonctionnent tout aussi bien.

SCRIPT:

 find /path/to/files/ -iname "*jpg" | \
  while read I; do
   cp -v --parent "$I" /backup/dir/
  done

Ici, je lis les fichiers en leur donnant le chemin d'accès et tout ce que je lis, je les garde dans une variable I, que je citerai plus tard en le traitant pour conserver les espaces afin de le traiter correctement.

J'espère que cela vous aide d'une manière ou d'une autre.


1
"tout est un fichier" fait référence à autre chose. Votre solution est spécifique à GUN et ne résiste pas à la barre oblique inverse ou aux caractères de nouvelle ligne dans les noms de fichiers. Les boucles "while read" dans les shells (IMO) sont souvent une indication d'une mauvaise pratique de script shell.
Stéphane Chazelas

@sch: hein, et je pensais, ça va. Merci de l'avoir signalé, j'ai probablement besoin de l'examiner davantage.
The Dark Knight

désolé, je voulais dire "GNU", pas "GUN" ci-dessus (c'est la première fois que je réalise que ce sont des anagrammes BTW ...)
Stéphane Chazelas

1

Simplement avec findet bash:

find . -name '*.jpg' -exec bash -c '
    echo "treating file $1 in bash, from path : ${1%/*}" 
' -- {} \;

De cette façon, nous utilisons $1en bash, comme dans un script de base, qui ouvrent de belles perspectives pour effectuer toutes les tâches avancées (ou non).

Un moyen plus efficace (pour éviter d'avoir à démarrer un nouveau bash pour chaque fichier):

find . -name '*.jpg' -exec bash -c 'for i do
    echo "treating file $i in bash, from path : ${i%/*}" 
  done' -- {} +

0

Dans la version récente de bash, vous pouvez utiliser l' globstaroption:

shopt -s globstar
for file in **/*.jpg ; do
    echo "$file"
done

Pour les actions simples, vous pouvez même ignorer complètement la boucle:

echo **/*.jpg

Bien que cela fonctionne dans zsh (d'où provient la fonctionnalité) et avec ksh93 (avec set -G), il ne devrait pas être utilisé dans bash car la version bash est fondamentalement cassée (tout comme GNU grep -r) car elle descend en liens symboliques vers des répertoires (équivalents à find -Lou zsh) ***/*.jpg). Notez également que contrairement à find, il supprimera les dotfiles et ne descendra pas dans les dotdirs (par défaut).
Stéphane Chazelas
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.