Les réponses acceptées et votées sont excellentes, mais il leur manque quelques détails essentiels. Ce post couvre les cas où l'expansion du nom du chemin de l'interpréteur de commandes (glob) échoue, où les noms de fichiers contiennent des nouvelles lignes/symboles de tirets intégrés et où la redirection de la sortie de la commande est déplacée hors de la boucle for lorsque les résultats sont écrits dans un fichier.
Lors de l'exécution de l'expansion du glob shell en utilisant *
il y a une possibilité que l'expansion échoue s'il y a pas de présents dans le répertoire et une chaîne glob non développée sera transmise à la commande à exécuter, ce qui pourrait avoir des résultats indésirables. Le site bash
L'interpréteur de commandes fournit une option étendue pour cela en utilisant nullglob
. Ainsi, la boucle devient essentiellement la suivante dans le répertoire contenant vos fichiers
shopt -s nullglob
for file in ./*; do
cmdToRun [option] -- "$file"
done
Cela vous permet de sortir en toute sécurité de la boucle for lorsque l'expression ./*
ne renvoie aucun fichier (si le répertoire est vide)
ou d'une manière conforme à POSIX ( nullglob
es bash
spécifique)
for file in ./*; do
[ -f "$file" ] || continue
cmdToRun [option] -- "$file"
done
Cela vous permet d'aller à l'intérieur de la boucle lorsque l'expression échoue pour une fois et que la condition [ -f "$file" ]
vérifie si la chaîne non expansée ./*
est un nom de fichier valide dans ce répertoire, ce qui ne serait pas le cas. Donc, en cas d'échec de cette condition, l'utilisation de continue
nous revenons à la for
boucle qui ne sera pas exécutée par la suite.
Notez également l'utilisation de --
juste avant de passer l'argument du nom de fichier. Ceci est nécessaire parce que, comme nous l'avons noté précédemment, les noms de fichiers de l'interpréteur de commandes peuvent contenir des tirets n'importe où dans le nom de fichier. Certaines commandes de l'interpréteur de commandes interprètent cela et les traitent comme une option de commande lorsque les noms sont no cité correctement et exécute la commande en pensant si le drapeau est fourni.
Le site --
signale la fin des options de la ligne de commande dans ce cas, ce qui signifie que la commande ne doit pas analyser les chaînes au-delà de ce point comme des drapeaux de commande mais seulement comme des noms de fichiers.
La double citation des noms de fichiers résout correctement les cas où les noms contiennent des caractères globaux ou des espaces blancs. Mais les noms de fichiers *nix peuvent aussi contenir des nouvelles lignes. Nous dé-limitons donc les noms de fichiers avec le seul caractère qui ne peut pas faire partie d'un nom de fichier valide - l'octet nul ( \0
). Puisque bash
utilise en interne C
dans lesquelles les octets nuls sont utilisés pour indiquer la fin de la chaîne, il est le bon candidat pour cela.
Ainsi, en utilisant le printf
du shell pour délimiter les fichiers avec cet octet NULL en utilisant l'option -d
option de read
nous pouvons faire ce qui suit
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
Le site nullglob
et le printf
sont enveloppés autour (..)
ce qui signifie qu'ils sont essentiellement exécutés dans un sous-shell (child shell), car pour éviter l'utilisation de la fonction nullglob
pour se répercuter sur le shell parent, une fois la commande quittée. L'adresse -d ''
option de read
La commande est no conforme à POSIX, et nécessite donc un bash
pour que cela soit fait. Utilisation de find
Cette commande peut être réalisée comme suit
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
Pour find
les implémentations qui ne supportent pas -print0
(autres que les implémentations GNU et FreeBSD), ceci peut être émulé en utilisant la commande printf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Une autre correction importante consiste à déplacer la redirection hors de la boucle for afin de réduire le nombre élevé d'entrées/sorties de fichiers. Lorsqu'il est utilisé à l'intérieur de la boucle, le shell doit exécuter des appels système deux fois pour chaque itération de la boucle for, une fois pour ouvrir et une fois pour fermer le descripteur de fichier associé au fichier. Cela deviendra un goulot d'étranglement pour vos performances lors de l'exécution de grandes itérations. La suggestion recommandée serait de le déplacer en dehors de la boucle.
En étendant le code ci-dessus avec ces corrections, vous pourriez faire
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
qui va essentiellement mettre le contenu de votre commande pour chaque itération de votre entrée de fichier dans le stdout et lorsque la boucle se termine, ouvrir le fichier cible une fois pour écrire le contenu du stdout et le sauvegarder. L'équivalent find
La version de la même chose serait
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
3 votes
Je voudrais ajouter à la question. Peut-on le faire en utilisant xargs ? par exemple,
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out
3 votes
C'est possible, mais vous avez probablement ne veulent pas utiliser
ls
pour conduirexargs
. Sicmd
est écrit de manière compétente, peut-être pouvez-vous simplement fairecmd <wildcard>
.