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
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
Que dis-tu de ça:
find /some/directory -maxdepth 1 -type f -exec cmd option {} \; > results.out
-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.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.
Je fais cela sur mon Raspberry Pi à partir de la ligne de commande en exécutant:
for i in *;do omxplayer "$i";done
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
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
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
je pense que la solution simple est:
sh /dir/* > ./result.txt
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
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
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
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