Je souhaite parcourir une liste de fichiers. Cette liste est le résultat d'une find
commande, j'ai donc trouvé:
getlist() {
for f in $(find . -iname "foo*")
do
echo "File found: $f"
# do something useful
done
}
C'est bien sauf si un fichier a des espaces dans son nom:
$ ls
foo_bar_baz.txt
foo bar baz.txt
$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt
Que puis-je faire pour éviter la division des espaces?
Réponses:
Vous pouvez remplacer l'itération basée sur les mots par une itération basée sur les lignes:
la source
touch "$(printf "foo\nbar")"
IFS= while read -r f
plutôt.find
et une boucle while.-exec
va être plus propre que une boucle explicite:find . -iname "foo*" -exec echo "File found: {}" \;
. De plus, dans de nombreux cas, vous pouvez remplacer ce dernier\;
par+
pour mettre beaucoup de fichiers dans la même commande.Il existe plusieurs façons pratiques d'y parvenir.
Si vous vouliez vous en tenir à votre version originale, cela pourrait être fait de cette façon:
Cela échouera toujours si les noms de fichiers contiennent des sauts de ligne littéraux, mais les espaces ne le cassent pas.
Cependant, jouer avec IFS n'est pas nécessaire. Voici ma façon préférée de le faire:
Si vous trouvez la
< <(command)
syntaxe peu familière, vous devriez lire à propos de la substitution de processus . L'avantage de cecifor file in $(find ...)
est que les fichiers avec des espaces, des retours à la ligne et d'autres caractères sont correctement gérés. Cela fonctionne carfind
avec-print0
utilisera unnull
(aka\0
) comme terminateur pour chaque nom de fichier et, contrairement à la nouvelle ligne, null n'est pas un caractère légal dans un nom de fichier.L'avantage de cela par rapport à la version presque équivalente
Est-ce que toute affectation de variable dans le corps de la boucle while est préservée. Autrement dit, si vous redirigez vers
while
comme ci-dessus, le corps duwhile
est dans un sous-shell qui peut ne pas être ce que vous voulez.L'avantage de la version de substitution de processus
find ... -print0 | xargs -0
est minime: laxargs
version est correcte si tout ce dont vous avez besoin est d'imprimer une ligne ou d'effectuer une seule opération sur le fichier, mais si vous devez effectuer plusieurs étapes, la version en boucle est plus facile.EDIT : Voici un joli script de test afin que vous puissiez avoir une idée de la différence entre les différentes tentatives de résolution de ce problème
la source
$IFS
la< <(cmd)
syntaxe. Reste encore une chose obscure pour moi, pourquoi l'$
en$'\0'
? Merci beaucoup.while IFS= read
... pour gérer les fichiers qui commencent ou se terminent par des espaces.bash
donc je me suis senti en sécurité en utilisant des fonctionnalités spécifiques à bash. La substitution de processus n'est pas portable pour d'autres shells (sh lui-même ne recevra probablement jamais une mise à jour aussi importante).IFS=$'\n'
avecfor
empêche le fractionnement de mots interne à la ligne, mais soumet toujours les lignes résultantes à la globalisation, donc cette approche n'est pas entièrement robuste (sauf si vous désactivez également la globalisation en premier). Bien que celaread -d $'\0'
fonctionne, il est légèrement trompeur en ce sens qu'il suggère que vous pouvez utiliser$'\0'
pour créer des NUL - vous ne pouvez pas: un\0
dans une chaîne entre guillemets C ANSI termine effectivement la chaîne, de sorte que-d $'\0'
c'est effectivement le même que-d ''
.Il existe également une solution très simple: s'appuyer sur bash globbing
Notez que je ne suis pas sûr que ce comportement soit le comportement par défaut mais je ne vois aucun paramètre spécial dans mon shopt donc j'irais dire qu'il devrait être "sûr" (testé sur osx et ubuntu).
la source
la source
Tu vois
man xargs
.la source
Étant donné que vous ne faites aucun autre type de filtrage avec
find
, vous pouvez utiliser ce qui suit à partir debash
4.0:Le
**/
correspondra à zéro ou plusieurs répertoires, donc le modèle complet correspondrafoo*
dans le répertoire actuel ou dans n'importe quel sous-répertoire.la source
J'aime vraiment les boucles et l'itération de tableau, donc je pense que j'ajouterai cette réponse au mélange ...
J'ai également aimé l'exemple de fichier stupide de marchelbling. :)
Dans le répertoire de test:
Cela ajoute chaque ligne de liste de fichiers dans un tableau bash nommé
arr
avec toute nouvelle ligne de fin supprimée.Disons que nous voulons donner à ces fichiers de meilleurs noms ...
$ {! arr [@]} se développe à 0 1 2 donc "$ {arr [$ i]}" est le i ème élément du tableau. Les guillemets autour des variables sont importants pour préserver les espaces.
Le résultat est trois fichiers renommés:
la source
find
a un-exec
argument qui fait une boucle sur les résultats de la recherche et exécute une commande arbitraire. Par exemple:Ici
{}
représente les fichiers trouvés, et leur encapsulation""
permet à la commande shell résultante de traiter les espaces dans le nom de fichier.Dans de nombreux cas, vous pouvez remplacer ce dernier
\;
(qui démarre une nouvelle commande) par un\+
, qui mettra plusieurs fichiers dans la même commande (pas nécessairement tous en même temps cependant, voirman find
pour plus de détails).la source
Dans certains cas, ici, si vous avez juste besoin de copier ou de déplacer une liste de fichiers, vous pouvez également diriger cette liste vers awk.
Important
\"" "\"
autour du terrain$0
(en bref vos fichiers, une liste de lignes = un fichier).la source
Ok - mon premier post sur Stack Overflow!
Bien que mes problèmes avec cela aient toujours été dans csh pas bash, la solution que je présente fonctionnera, j'en suis sûr, dans les deux. Le problème est avec l'interprétation du shell des retours "ls". Nous pouvons supprimer "ls" du problème en utilisant simplement l'extension shell du
*
caractère générique - mais cela donne une erreur "no match" s'il n'y a aucun fichier dans le dossier actuel (ou spécifié) - pour contourner ce problème, nous étendons simplement le expansion pour inclure des fichiers dot ainsi:* .*
- cela donnera toujours des résultats depuis les fichiers. et .. sera toujours présent. Donc, dans csh, nous pouvons utiliser cette construction ...si vous voulez filtrer les fichiers dot standard, c'est assez simple ...
Le code dans le premier post sur ce fil serait écrit ainsi: -
J'espère que cela t'aides!
la source
Une autre solution pour le travail ...
L'objectif était:
la source