399 votes

Exécuter la commande sur tous les fichiers d'un répertoire

Quelqu'un pourrait-il fournir le code permettant de faire ce qui suit ? Supposons qu'il existe un répertoire de fichiers, qui doivent tous être exécutés par un programme. Le programme sort les résultats en sortie standard. J'ai besoin d'un script qui ira dans un répertoire, exécutera la commande sur chaque fichier, et conciliera la sortie dans un grand fichier de sortie.

Par exemple, pour exécuter la commande sur 1 fichier :

$ cmd [option] [filename] > 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 conduire xargs . Si cmd est écrit de manière compétente, peut-être pouvez-vous simplement faire cmd <wildcard> .

563voto

Andrew Logvinov Points 7291

Le code bash suivant passera $file à la commande où $file représentera chaque fichier dans /dir.

for file in /dir/*
do
  cmd [option] "$file" >> results.out
done

Exemple

el@defiant ~/foo $ touch foo.txt bar.txt baz.txt
el@defiant ~/foo $ for i in *.txt; do echo "hello $i"; done
hello bar.txt
hello baz.txt
hello foo.txt

33 votes

Si aucun fichier n'existe dans /dir/ alors la boucle s'exécute encore une fois avec une valeur de '*' pour les éléments suivants $file ce qui peut être indésirable. Pour éviter cela, activez nullglob pour la durée de la boucle. Ajoutez cette ligne avant la boucle shopt -s nullglob et cette ligne après la boucle shopt -u nullglob #revert nullglob back to it's normal default state .

0 votes

Si le fichier de sortie est le même à l'intérieur de la boucle, il est beaucoup plus efficace de rediriger à l'extérieur de la boucle. done >results.out (et probablement alors vous pouvez écraser au lieu d'ajouter, comme je l'ai supposé ici).

0 votes

Comment obtenir des fichiers de résultats individuels dont le nom correspond à celui de leur fichier d'entrée ?

231voto

Jim Lewis Points 18753

Que dites-vous de ça ?

find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
  • -maxdepth 1 empêche find de descendre récursivement dans tous les sous-répertoires. (Si vous voulez que de tels répertoires imbriqués soient traités, vous pouvez omettre cet argument).
  • -type -f spécifie que seuls les fichiers ordinaires seront traités.
  • -exec cmd option {} lui dit d'exécuter cmd avec l'option spécifiée option pour chaque fichier trouvé, le nom du fichier étant remplacé par {}
  • \; indique la fin de la commande.
  • Enfin, la production de tous les individus cmd est redirigée vers results.out

Cependant, si vous vous souciez de l'ordre dans lequel les dossiers sont traités, vous vous feriez mieux d'écrire une boucle. Je pense que find traite les fichiers dans l'ordre des inodes (mais je peux me tromper), ce qui peut ne pas être ce que vous voulez.

1 votes

C'est la manière correcte de traiter les fichiers. L'utilisation d'une boucle for est source d'erreurs pour de nombreuses raisons. Le tri peut également être effectué en utilisant d'autres commandes telles que stat y sort ce qui, bien sûr, dépend des critères de tri.

2 votes

Si je voulais exécuter deux commandes, comment les lier après la commande -exec option ? Dois-je les mettre entre guillemets ou autre ?

0 votes

find est toujours la meilleure option car vous pouvez filtrer par motif de nom de fichier avec l'option -name et vous pouvez le faire en une seule commande.

102voto

robgraves Points 815

Je fais cela sur mon raspberry pi à partir de la ligne de commande en exécutant :

for i in *;do cmd "$i";done

2 votes

Alors que cette réponse est probablement la "bonne" façon de procéder dans un environnement de production, mais pour la commodité de l'utilisation quotidienne, cette ligne unique l'emporte !

2 votes

En fait, je pense que ce serait plus lisible avec des espaces après les points-virgules, mais c'est peut-être juste moi !

16voto

Inian Points 36223

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

1 votes

+1 pour vérifier que le fichier existe. Si la recherche se fait dans un répertoire non existant, $file contient la chaîne regex "/invald_dir/*" qui n'est pas un nom de fichier valide.

14voto

Al Mamun Points 351

Vous pouvez utiliser xarg :

ls | xargs -L 1 -d '\n' your-desired-command 
  • -L 1 fait passer 1 élément à la fois

  • -d '\n' divise la sortie de ls sur la base de la nouvelle ligne.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X