Les réponses acceptées / à vote élevé sont excellentes, mais elles manquent de quelques détails concrets. Cette publication couvre les cas sur la façon de mieux gérer lorsque l'expansion du nom de chemin du shell (glob) échoue, lorsque les noms de fichiers contiennent des sauts de ligne / symboles de tiret et le déplacement de la direction de sortie de la commande hors de la boucle for lors de l'écriture des résultats dans un fichier.
Lors de l'exécution de l'expansion du shell shell à l'aide de l'extension, *
il est possible que l'expansion échoue s'il n'y a aucun fichier 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 bash
shell fournit une option shell étendue pour cette utilisation 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 quitter en toute sécurité la boucle for lorsque l'expression ./*
ne renvoie aucun fichier (si le répertoire est vide)
ou d'une manière conforme POSIX ( nullglob
est 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 la condition [ -f "$file" ]
vérifie si la chaîne non développée ./*
est un nom de fichier valide dans ce répertoire, ce qui ne le serait pas. Donc, en cas d'échec de cette condition, en utilisant continue
nous reprenons la for
boucle qui ne s'exécutera pas par la suite.
Notez également l'utilisation de --
juste avant de passer l'argument du nom de fichier. Cela est nécessaire car, comme indiqué précédemment, les noms de fichiers du shell peuvent contenir des tirets n'importe où dans le nom de fichier. Certaines des commandes shell l'interprètent et les traitent comme une option de commande lorsque le nom n'est pas cité correctement et exécute la commande en pensant si l'indicateur est fourni.
Le --
signale la fin des options de ligne de commande dans ce cas, ce qui signifie que la commande ne doit pas analyser les chaînes au-delà de ce point en tant que drapeaux de commande, mais uniquement en tant que noms de fichiers.
La citation double 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 également contenir des retours à la ligne. Nous 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
). Comme il bash
utilise en interne des C
chaînes de style dans lesquelles les octets nuls sont utilisés pour indiquer la fin de la chaîne, c'est le bon candidat pour cela.
Donc, en utilisant l' printf
option shell pour délimiter les fichiers avec cet octet NULL en utilisant l' -d
option de read
commande, nous pouvons faire ci-dessous
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done
Le nullglob
et le printf
sont entourés, (..)
ce qui signifie qu'ils sont essentiellement exécutés dans un sous-shell (shell enfant), car pour éviter l' nullglob
option de réfléchir sur le shell parent, une fois la commande terminée. L' -d ''
option de read
commande n'est pas compatible POSIX, a donc besoin d'un bash
shell pour cela. En utilisant la find
commande, cela peut être fait comme
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)
Pour les find
implémentations qui ne prennent pas en charge -print0
(autres que les implémentations GNU et FreeBSD), cela peut être émulé en utilisantprintf
find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --
Un autre correctif important consiste à déplacer la redirection hors de la boucle for pour réduire un nombre élevé d'E / S 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 sur vos performances pour l'exécution de grandes itérations. La suggestion recommandée serait de le déplacer hors de la boucle.
En étendant le code ci-dessus avec ces correctifs, vous pouvez le faire
( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
cmdToRun [option] -- "$file"
done > results.out
qui placera essentiellement le contenu de votre commande pour chaque itération de votre entrée de fichier dans stdout et lorsque la boucle se terminera, ouvrez le fichier cible une fois pour écrire le contenu de la stdout et l'enregistrer. La find
version équivalente de la même serait
while IFS= read -r -d '' file; do
cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
ls <directory> | xargs cmd [options] {filenames put in here automatically by xargs} [more arguments] > results.out