Pipe vers plusieurs fichiers dans le shell

29

J'ai une application qui produira une grande quantité de données que je ne souhaite pas stocker sur le disque. L'application génère principalement des données que je ne souhaite pas utiliser, mais un ensemble d'informations utiles qui doivent être divisées en fichiers séparés. Par exemple, étant donné la sortie suivante:

JUNK
JUNK
JUNK
JUNK
A 1
JUNK
B 5
C 1
JUNK

Je pourrais exécuter l'application trois fois comme ceci:

./app | grep A > A.out
./app | grep B > B.out
./app | grep C > C.out

Cela m'obtiendrait ce que je veux, mais cela prendrait trop de temps. Je ne veux pas non plus sauvegarder toutes les sorties dans un seul fichier et les analyser.

Existe-t-il un moyen de combiner les trois opérations illustrées ci-dessus de telle manière que je n'ai besoin d'exécuter l'application qu'une seule fois et d'obtenir toujours trois fichiers de sortie distincts?

sj755
la source

Réponses:

78

Si vous avez un tee

./app | tee >(grep A > A.out) >(grep B > B.out) >(grep C > C.out) > /dev/null

(d' ici )

propos de la substitution de processus )

Aurélien Ooms
la source
4
Génial, cela pourrait aussi être rendu comme:./app | tee >(grep A > A.out) >(grep B > B.out) | grep C > C.out
evilsoup
7
Cette réponse est actuellement la seule exacte, étant donné le titre original de la question "canaliser vers plusieurs processus".
acelent
3
+1. C'est la réponse la plus généralement applicable, car elle ne dépend pas du fait que la commande de filtrage spécifique était grep.
ruakh
1
Je conviens que c'est la meilleure réponse à la question posée et qu'elle doit être marquée ainsi. Parallèle est une autre solution (telle que publiée), mais après avoir effectué quelques comparaisons chronométrées, l'exemple ci-dessus est plus efficace. Si l'op impliquait à la place des opérations très intensives en CPU telles que la compression de fichiers multiples ou la conversion mp3 multiple, alors la solution parallèle devrait sans aucun doute s'avérer plus efficace.
AsymLabs
32

Vous pouvez utiliser awk

./app | awk '/A/{ print > "A.out"}; /B/{ print > "B.out"}; /C/{ print > "C.out"}'
Rahul Patil
la source
6
Le titre de la question est pipe vers plusieurs processus , cette réponse concerne le "piping" (répartition par regex) vers plusieurs fichiers . Cette réponse ayant été acceptée, le titre de la question devrait être modifié en conséquence.
acelent
@PauloMadeira Vous avez raison. Que pensez-vous serait un meilleur titre?
sj755
J'ai suggéré une toute petite modification "Pipe vers plusieurs fichiers dans le shell", il est en attente de révision, vérifiez-le. Je m'attendais à supprimer le commentaire s'il était accepté.
acelent
@PauloMadeira - J'ai changé de titre. Vous n'avez pas vu votre modification, mais vous avez raison, l'utilisation des processus dans le titre était incorrecte s'il s'agit de la réponse acceptée.
slm
17

Vous pouvez également utiliser les capacités de correspondance de motifs de votre coque :

./app | while read line; do 
     [[ "$line" =~ A ]] && echo $line >> A.out; 
     [[ "$line" =~ B ]] && echo $line >> B.out; 
     [[ "$line" =~ C ]] && echo $line >> C.out; 
 done

Ou même:

./app | while read line; do for foo in A B C; do 
     [[ "$line" =~ "$foo" ]] && echo $line >> "$foo".out; 
  done; done

Un moyen plus sûr qui peut gérer les barres obliques inverses et les lignes commençant par -:

./app | while IFS= read -r line; do for foo in A B C; do 
     [[ "$line" =~ "$foo" ]] && printf -- "$line\n" >> "$foo".out; 
  done; done

Comme le souligne @StephaneChazelas dans les commentaires, ce n'est pas très efficace. La meilleure solution est probablement @ AurélienOoms ' .

terdon
la source
Cela suppose que l'entrée ne contient pas de barres obliques inverses ou des blancs ou des caractères génériques, ou des lignes qui commencent par -n, -e... Cela va également être terriblement inefficace car cela signifie plusieurs appels système par ligne (un read(2)par caractère, le fichier étant ouvert, l'écriture fermé pour chaque ligne ...). En règle générale, l'utilisation de while readboucles pour traiter du texte dans des shells est une mauvaise pratique.
Stéphane Chazelas
@StephaneChazelas J'ai édité ma réponse. Cela devrait fonctionner avec les barres obliques inverses et -netc. maintenant. Pour autant que je sache, les deux versions fonctionnent bien avec des blancs, ai-je tort?
terdon
Non, le premier argument de printfest le format. Il n'y a aucune raison de vous laisser des variables sans guillemets là-dedans.
Stéphane Chazelas
Cela interrompra également bash (et d'autres shells qui utilisent les chaînes de caractères de la même manière) s'il y a des valeurs nulles dans l'entrée.
Chris Down
9

Si vous avez plusieurs cœurs et que vous souhaitez que les processus soient en parallèle, vous pouvez faire:

parallel -j 3 -- './app | grep A > A.out' './app | grep B > B.out' './app | grep C > C.out'

Cela engendrera trois processus dans des cœurs parallèles. Si vous voulez qu'il y ait une sortie vers la console, ou un fichier maître, cela a l'avantage de garder la sortie dans un certain ordre, plutôt que de la mélanger.

L'utilitaire gnu parallèle d'Ole Tange peut être obtenu auprès de la plupart des dépôts sous le nom parallèle ou moreutils . La source peut être obtenue sur Savannah.gnu.org . Une vidéo d'introduction est également disponible ici .

Addenda

En utilisant la version la plus récente de Parallel (pas nécessairement la version de votre référentiel de distribution), vous pouvez utiliser la construction la plus élégante:

./app | parallel -j3 -k --pipe 'grep {1} >> {1}.log' ::: 'A' 'B' 'C'

Ce qui permet d'obtenir le résultat d'exécuter un ./app et 3 processus grep parallèles dans des cœurs ou des threads séparés (comme déterminé par parallèle lui-même, considérez également le -j3 comme facultatif, mais il est fourni dans cet exemple à des fins instructives).

La nouvelle version de Parallel peut être obtenue en faisant:

wget http://ftpmirror.gnu.org/parallel/parallel-20131022.tar.bz2

Ensuite, décompressez d'habitude, cd vers parallel- {date}, ./configure && make, sudo make install. Ceci installera parallèle, page de manuel parallèle et page de manuel parallel_tutorial.

AsymLabs
la source
7

En voici un en Perl:

./app | perl -ne 'BEGIN {open(FDA, ">A.out") and 
                         open(FDB, ">B.out") and 
                         open(FDC, ">C.out") or die("Cannot open files: $!\n")} 
                  print FDA $_ if /A/; print FDB $_ if /B/; print FDC $_ if /C/'
troydj
la source
1
sed -ne/A/w\ A.out -e/B/w\ B.out -e/C/p <in >C.out

... if <inest lisible, les trois fichiers de sortie seront tronqués avant que quoi que ce soit ne leur soit écrit.

mikeserv
la source