Comment puis-je envoyer stdout à plusieurs commandes?

187

Il y a des commandes qui filtrent ou agissent en entrée, puis les transmettent en sortie, je pense en général stdout, mais certaines commandes prennent juste le stdinet font ce qu’elles font, sans rien sortir.

Je suis plus familier avec OS X et donc il y a deux qui me viennent immédiatement à l' esprit sont pbcopyet pbpaste- qui sont des moyens d'accès au presse - papiers du système.

Quoi qu'il en soit, je sais que si je veux prendre stdout et cracher la sortie pour aller aux deux stdoutet à un fichier, je peux utiliser la teecommande. Et j'en connais un peu xargs, mais je ne pense pas que ce soit ce que je recherche.

Je veux savoir comment je peux me séparer stdoutpour aller entre deux commandes (ou plus). Par exemple:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

Il y a probablement un meilleur exemple que celui-là, mais je suis vraiment intéressé à savoir comment envoyer stdout à une commande qui ne le relayera pas et tout en évitant stdoutd'être "muet" - je ne vous demande pas comment créer catun fichier; grepune partie de celui-ci et copiez-le dans le presse-papiers - les commandes spécifiques ne sont pas si importants.

Aussi - je ne demande pas comment envoyer cela dans un fichier et stdout- cela peut être une question "en double" (désolé), mais j'en ai cherché et je n'ai trouvé que des questions similaires qui demandaient comment diviser entre stdout et un fichier - et les réponses à ces questions semblaient être tee, ce qui, à mon avis, ne fonctionnera pas pour moi.

Enfin, vous pouvez vous demander "pourquoi ne pas simplement faire de pbcopy la dernière chose dans la chaîne de tubes?" et ma réponse est 1) si je veux l’utiliser et voir toujours la sortie dans la console? 2) Que faire si je veux utiliser deux commandes qui ne sortent pas stdoutaprès avoir traité l'entrée?

Oh, et encore une chose - je me rends compte que je pourrais utiliser teeun tube nommé ( mkfifo) mais j'espérais pouvoir le faire en ligne, de manière concise, sans configuration préalable :)

cwd
la source
Duplication possible de unix.stackexchange.com/questions/26964/…
Nikhil Mulley

Réponses:

241

Vous pouvez utiliser teeet traiter la substitution pour cela:

cat file.txt | tee >(pbcopy) | grep errors

Cela enverra toute la sortie de cat file.txtà pbcopy, et vous n'obtiendrez que le résultat de grepsur votre console.

Vous pouvez mettre plusieurs processus dans la teepièce:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors
Tapis
la source
21
Il n’est pas préoccupant pbcopy, mais mérite d’être mentionné en général: quels que soient les résultats de substitution de processus , le segment de conduite suivant voit également les résultats , après l’entrée initiale; Par exemple: seq 3 | tee >(cat -n) | cat -e( cat -nnumérote les lignes d'entrée, cat -emarque les nouvelles lignes avec $; vous verrez qu'il cat -eest appliqué à la fois à l'entrée d'origine (la première) et à la sortie (ensuite) cat -n). La sortie de plusieurs substitutions de processus arrivera dans un ordre non déterministe.
mklement0
49
Le >(seul fonctionne dans bash. Si vous essayez par exemple d’utiliser shcela ne fonctionnera pas. Il est important de faire cet avis.
AAlvz
10
@AAlvz: Bon point: la substitution de processus n'est pas une fonctionnalité POSIX; dash, qui agit comme shsur Ubuntu, ne le supporte pas, et même Bash lui-même désactive la fonctionnalité quand elle est appelée shou quand set -o posixest en vigueur. Cependant, Bash n'est pas le seul à prendre en charge les substitutions de processus: kshet à les zshsoutenir également (pas sûr des autres).
mklement0
2
@ mklement0 cela ne semble pas être vrai. Sur zsh (Ubuntu 14.04), votre ligne affiche: 1 1 2 2 3 3 1 $ 2 $ 3 $ Ce qui est triste, car je voulais vraiment que les fonctionnalités soient comme vous le dites.
Aktau
2
@Aktau: En effet, mon exemple de commande fonctionne uniquement comme décrit dans bashet ksh- zshapparemment, il n'envoie pas de sortie de substitutions de processus de sortie via le pipeline (sans doute, c'est préférable , car il ne pollue pas ce qui est envoyé au segment de pipeline suivant - bien que il imprime toujours ). Cependant, dans tous les shells mentionnés, ce n’est généralement pas une bonne idée d’avoir un seul pipeline dans lequel les sorties stdout et normales des substitutions de processus sont mélangées - l’ordre de sortie ne sera pas prévisible, de manière à ne faire surface que rarement ou avec de gros ensembles de données de sortie.
mklement0
124

Vous pouvez spécifier plusieurs noms de fichier teeet, en plus, la sortie standard peut être canalisée en une seule commande. Pour répartir la sortie sur plusieurs commandes, vous devez créer plusieurs canaux et spécifier chacun d'eux comme une sortie de tee. Il y a plusieurs moyens de le faire.

Substitution de processus

Si votre shell est ksh93, bash ou zsh, vous pouvez utiliser la substitution de processus. C'est un moyen de transmettre un canal à une commande qui attend un nom de fichier. Le shell crée le canal et attribue un nom de fichier similaire /dev/fd/3à la commande. Le numéro correspond au descripteur de fichier auquel le canal est connecté. Certaines variantes unix ne supportent pas /dev/fd; sur ceux-ci, un canal nommé est utilisé à la place (voir ci-dessous).

tee >(command1) >(command2) | command3

Descripteurs de fichier

Dans n'importe quel shell POSIX, vous pouvez utiliser explicitement plusieurs descripteurs de fichier . Cela nécessite une variante unix compatible /dev/fd, car toutes les sorties sauf une teedoivent être spécifiées par leur nom.

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

Tuyaux nommés

La méthode la plus élémentaire et portable consiste à utiliser des canaux nommés . L'inconvénient est que vous devez trouver un répertoire accessible en écriture, créer les tuyaux et nettoyer par la suite.

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2
Gilles
la source
10
Merci beaucoup d'avoir fourni les deux versions alternatives pour ceux qui ne veulent pas compter sur bash ou un certain ksh.
Trr
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3devrait sûrement être command3 | tee "$tmp_dir/f1" "$tmp_dir/f2", comme vous voulez stdout de command3sifflé à tee, non? J'ai testé votre version dashet teebloqué dans l'attente d'une entrée indéfinie, mais le changement d'ordre a donné le résultat attendu.
Adrian Günter
1
@ AdrianGünter Non. Les trois exemples lisent les données de l’entrée standard et les envoient à chacun de command, command2et command3.
Gilles
@Gilles, je vois, j'ai mal interprété l'intention et j'ai essayé d'utiliser le fragment de manière incorrecte. Merci pour la clarification!
Adrian Günter
Si vous n'avez aucun contrôle sur le shell utilisé, mais que vous pouvez utiliser bash explicitement, vous pouvez le faire <command> | bash -c 'tee >(command1) >(command2) | command3'. Cela m'a aidé dans mon cas.
gc5
16

Il suffit de jouer avec la substitution de processus.

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grepsont deux fichiers binaires qui ont la même sortie mycommand_execque leur entrée spécifique au processus.

Nikhil Mulley
la source
16

Si vous utilisez zshalors vous pouvez profiter de la puissance de la MULTIOSfonctionnalité, c'est-à-dire vous débarrasser teecomplètement de la commande:

uname >file1 >file2

va simplement écrire la sortie de unamedeux fichiers différents: file1et file2, ce qui est équivalent àuname | tee file1 >file2

De même la redirection des entrées standard

wc -l <file1 <file2

est équivalent à cat file1 file2 | wc -l(veuillez noter qu'il ne s'agit pas de la même chose que wc -l file1 file2, le dernier compte le nombre de lignes dans chaque fichier séparément).

Bien sûr, vous pouvez également utiliser MULTIOSpour rediriger la sortie non vers des fichiers, mais vers d'autres processus, en utilisant la substitution de processus, par exemple:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')
Jimmij
la source
3
Bon à savoir. MULTIOSest une option activée par défaut (et pouvant être désactivée avec unsetopt MULTIOS).
mklement0
6

Pour une sortie raisonnablement petite produite par une commande, nous pouvons rediriger la sortie vers un fichier temporaire et envoyer ce fichier temporaire à des commandes en boucle. Cela peut être utile lorsque l’ordre des commandes exécutées est important.

Le script suivant, par exemple, pourrait le faire:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

Test exécuté sur Ubuntu 16.04 avec /bin/shcomme dashshell:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash
Sergiy Kolodyazhnyy
la source
5

Capturez la commande STDOUTavec une variable et réutilisez-la autant de fois que vous le souhaitez:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

Si vous avez également besoin de capturer STDERR, utilisez 2>&1à la fin de la commande, comme suit:

commandoutput="$(command-to-run 2>&1)"
laebshade
la source
3
Où sont stockées les variables? Si vous avez affaire à un fichier volumineux ou quelque chose du genre, cela ne va-t-il pas monopoliser beaucoup de mémoire? Les variables sont-elles limitées en taille?
cwd
1
Et si $ commandoutput est énorme?, il vaut mieux utiliser des pipes et des substitutions de processus.
Nikhil Mulley
4
Évidemment, cette solution n'est possible que lorsque vous savez que la taille de la sortie tiendra facilement dans la mémoire et que vous pouvez mettre en mémoire tampon la totalité de la sortie avant d'exécuter les commandes suivantes. Les tubes résolvent ces deux problèmes en permettant des données de longueur arbitraire et en les diffusant en temps réel au récepteur au fur et à mesure de leur génération.
trr
2
C'est une bonne solution si vous avez une petite sortie et que vous savez que la sortie sera textuelle et non binaire. (les variables shell ne sont souvent pas sûres en binaire)
Rucent88
1
Je n'arrive pas à faire fonctionner cela avec des données binaires. Je pense que c'est quelque chose avec l'écho qui tente d'interpréter des octets nuls ou d'autres données non caractéristiques.
Rolf
0

Voici une solution partielle rapide, compatible avec tout shell, y compris busybox.

Le problème plus étroit qu’il résout est le suivant: imprimez le stdoutfichier complet sur une console et filtrez-le sur une autre, sans fichiers temporaires ni canaux nommés.

  • Démarrer une autre session sur le même hôte. Pour trouver son nom TTY, tapez tty. Supposons /dev/pty/2.
  • Dans la première session, lancez the_program | tee /dev/pty/2 | grep ImportantLog:

Vous obtenez un journal complet et un journal filtré.

Victor Sergienko
la source