web-dev-qa-db-fra.com

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

Quelqu'un pourrait-il fournir le code permettant d'effectuer les opérations suivantes: Supposons qu'il existe un répertoire de fichiers, qui doivent tous être exécutés via un programme. Le programme renvoie les résultats à la sortie standard. J'ai besoin d'un script qui ira dans un répertoire, exécutera la commande sur chaque fichier et concaténer la sortie dans un gros fichier de sortie.

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

$ cmd [option] [filename] > results.out
239
themaestro

Le code bash suivant passera $ file à commande où $ file représentera tous les fichiers de/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
359
Andrew Logvinov

Que dis-tu de ça:

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

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

152
Jim Lewis

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

for i in *;do omxplayer "$i";done
45
robgraves

J'avais besoin de copier tous les fichiers .md d'un répertoire à un autre, alors voici ce que j'ai fait.

for i in **/*.md;do mkdir -p ../docs/"$i" && rm -r ../docs/"$i" && cp "$i" "../docs/$i" && echo "$i -> ../docs/$i"; done

Ce qui est assez difficile à lire, alors décomposons-le.

premier cd dans le répertoire avec vos fichiers,

for i in **/*.md; pour chaque fichier de votre modèle

mkdir -p ../docs/"$i"faire ce répertoire dans un dossier docs en dehors du dossier contenant vos fichiers. Ce qui crée un dossier supplémentaire portant le même nom que ce fichier.

rm -r ../docs/"$i" supprime le dossier supplémentaire créé à la suite de mkdir -p

cp "$i" "../docs/$i" Copier le fichier actuel

echo "$i -> ../docs/$i" Faites écho à ce que vous avez fait

; done Vivre heureux pour toujours

2
Eric Wooley

Une façon rapide et sale de faire le travail est parfois:

find directory/ | xargs  Command 

Par exemple, pour trouver le nombre de lignes dans tous les fichiers du répertoire actuel, vous pouvez effectuer les opérations suivantes:

find . | xargs wc -l
2
Rahul

Basé sur l'approche de @Jim Lewis:

Voici une solution rapide en utilisant find et en triant les fichiers par date de modification:

$ find  directory/ -maxdepth 1 -type f -print0 | \
  xargs -r0 stat -c "%y %n" | \
  sort | cut -d' ' -f4- | \
  xargs -d "\n" -I{} cmd -op1 {} 

Pour le tri voir:

http://www.commandlinefu.com/commands/view/5720/find-files-and-list-them-sorted-by-modification-time

1
tuxdna

je pense que la solution simple est:

sh /dir/* > ./result.txt
1
yovie

Profondeur max

J'ai trouvé que cela fonctionnait bien avec la réponse de Jim Lewis, ajoutez juste un peu comme ceci:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
$ find . -maxdepth 1 -type f -name '*.sh' -exec {} \; > results.out

Ordre de tri

Si vous voulez exécuter dans l'ordre de tri, modifiez le comme ceci:

$ export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -maxdepth 2 -type f -name '*.sh' | sort | bash > results.out

Juste pour un exemple, ceci sera exécuté dans l'ordre suivant:

bash: 1: ./assets/main.sh
bash: 2: ./builder/clean.sh
bash: 3: ./builder/concept/compose.sh
bash: 4: ./builder/concept/market.sh
bash: 5: ./builder/concept/services.sh
bash: 6: ./builder/curl.sh
bash: 7: ./builder/identity.sh
bash: 8: ./concept/compose.sh
bash: 9: ./concept/market.sh
bash: 10: ./concept/services.sh
bash: 11: ./product/compose.sh
bash: 12: ./product/market.sh
bash: 13: ./product/services.sh
bash: 14: ./xferlog.sh

Profondeur illimitée

Si vous voulez exécuter en profondeur illimitée sous certaines conditions, vous pouvez utiliser ceci:

export DIR=/path/dir && cd $DIR && chmod -R +x *
find . -type f -name '*.sh' | sort | bash > results.out

puis mettez en haut de chaque fichier dans les répertoires enfants comme ceci:

#!/bin/bash
[[ "$(dirname `pwd`)" == $DIR ]] && echo "Executing `realpath $0`.." || return

et quelque part dans le corps du fichier parent:

if <a condition is matched>
then
    #execute child files
    export DIR=`pwd`
fi
1
Chetabahana

Les réponses acceptées/à vote élevé sont excellentes, mais il leur manque quelques détails concrets. Cet article traite des cas permettant de mieux gérer les cas d'échec de l'expansion du nom de chemin du shell, les noms de fichiers contenant des traits nouveaux/des tirets incorporés et le déplacement de la direction de sortie de la commande hors de la boucle for.

Lors de l'exécution de l'extension globale du shell à l'aide de *, il est possible que l'extension échoue si des fichiers no sont présents dans le répertoire et qu'une chaîne globale non développée est transmise à la commande. exécuter sur le fichier qui pourrait avoir des résultats indésirables. Le shell bash fournit une option de shell étendue à l'aide de nullglob. Donc, la boucle devient fondamentalement 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 la boucle for en toute sécurité lorsque l'expression ./* renvoie des fichiers (si le répertoire est vide).

ou d'une manière compatible 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 une fois et que 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 serait pas le cas. Donc, sur cette condition d'échec, en utilisant continue, nous revenons à la boucle for 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 contiennent des tirets n'importe où dans le nom du fichier. Certaines des commandes du shell interprètent cela, les traitent comme une option de commande et l'exécutent en pensant que l'indicateur est fourni.

-- signale la fin des options de ligne de commande dans ce cas, ce qui signifie que la commande ne doit analyser aucune chaîne au-delà de ce point en tant qu'indicateurs 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. Mais les noms de fichiers * nix peuvent également contenir des nouvelles lignes. 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). Puisque bash utilisé en interne C chaînes de style dans lesquelles les octets nuls sont utilisés pour indiquer la fin de la chaîne, il s'agit du bon candidat pour cela.

Donc, en utilisant l'option printf de Shell pour délimiter les fichiers avec cet octet NULL en utilisant l'option -d de la commande read, on peut faire ci-dessous

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done

Les variables nullglob et printf sont entourées de (..), ce qui signifie qu'elles sont généralement exécutées dans un sous-shell (shell enfant), car elles évitent l'option nullglob de réfléchir Shell parent, une fois la commande terminée. L'option -d '' de la commande read est pas conforme à POSIX. Un shell bash est donc nécessaire pour que cela soit effectué. En utilisant la commande find cela peut être fait comme

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0)

Pour les implémentations find qui ne supportent pas -print0 (autres que les implémentations GNU et FreeBSD), cela peut être émulé à l'aide de printf

find . -maxdepth 1 -type f -exec printf '%s\0' {} \; | xargs -0 cmdToRun [option] --

Un autre correctif important consiste à déplacer la re-direction en dehors de la boucle for afin de 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 deux fois des appels système pour chaque itération de la boucle for, une fois pour l'ouverture et une fois pour la fermeture du descripteur de fichier associé au fichier. Cela deviendra un gâchis pour votre performance lorsque vous exécutez 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 ce correctif, vous pourriez faire

( shopt -s nullglob; printf '%s\0' ./* ) | while read -rd '' file; do
    cmdToRun [option] -- "$file"
done > results.out

qui mettra fondamentalement le contenu de votre commande pour chaque itération de votre entrée de fichier sur stdout et lorsque la boucle sera terminée, ouvrez le fichier cible une fois pour écrire le contenu du stdout et le sauvegarder. La version équivalente find de la même serait

while IFS= read -r -d '' file; do
    cmdToRun [option] -- "$file"
done < <(find -maxdepth 1 -type f -print0) > results.out
0
Inian