web-dev-qa-db-fra.com

Comment envoyer stdout à plusieurs commandes?

Il y a certaines commandes qui filtrent ou agissent sur l'entrée, puis la transmettent en sortie, je pense généralement à stdout - mais certaines commandes prendront simplement le stdin et feront tout ce qu'elles feront avec et ne rien produire.

Je connais le mieux OS X et il y en a donc deux qui me viennent immédiatement à l'esprit: pbcopy et pbpaste- qui sont des moyens d'accéder 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 à la fois à stdout et à un fichier, je peux utiliser la commande tee. Et je connais un peu xargs, mais je ne pense pas que ce soit ce que je recherche.

Je veux savoir comment je peux diviser stdout pour passer entre deux (ou plus) commandes. Par exemple:

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

Il y a probablement un meilleur exemple que celui-ci, mais je suis vraiment intéressé de savoir comment envoyer stdout à une commande qui ne la relaie pas et tout en empêchant stdout d'être "muet" - je ne demande pas comment cat un fichier et grep une partie de celui-ci et le copier dans le presse-papiers - les commandes spécifiques ne sont pas si importantes.

Aussi - je ne demande pas comment envoyer ceci dans un fichier et stdout - cela peut être une question "en double" (désolé) mais j'ai fait quelques recherches et n'ai pu trouver que des questions similaires qui demandaient comment divisé entre stdout et un fichier - et les réponses à ces questions semblaient être tee, ce qui, je pense, 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 tuyaux?" et ma réponse est 1) et si je veux l'utiliser et voir toujours la sortie dans la console? 2) que faire si je veux utiliser deux commandes qui ne produisent pas stdout après avoir traité l'entrée?

Oh, et encore une chose - je me rends compte que je pourrais utiliser tee et un canal nommé (mkfifo) mais j'espérais que cela pourrait être fait en ligne, de manière concise, sans configuration préalable: )

200
cwd

Vous pouvez utiliser tee et effectuer une substitution de processus pour cela:

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

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

Vous pouvez placer plusieurs processus dans la partie tee:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors
251
Mat

Vous pouvez spécifier plusieurs noms de fichiers sur tee et, en outre, la sortie standard peut être canalisée dans une seule commande. Pour répartir la sortie vers 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. Il s'agit d'un moyen de passer un canal à une commande qui attend un nom de fichier. Le shell crée le canal et passe un nom de fichier comme /dev/fd/3 à la commande. Le nombre est le descripteur de fichier auquel le tuyau est connecté. Certaines variantes Unix ne prennent pas en charge /dev/fd; sur ceux-ci, un tube nommé est utilisé à la place (voir ci-dessous).

tee >(command1) >(command2) | command3

Descripteurs de fichiers

Dans n'importe quel shell POSIX, vous pouvez utiliser plusieurs descripteurs de fichiers explicitement. Cela nécessite une variante Unix qui prend en charge /dev/fd, car toutes les sorties de tee sauf une doivent ê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 simple et la plus portable consiste à utiliser tubes nommés . L'inconvénient est que vous devez trouver un répertoire accessible en écriture, créer les canaux et nettoyer ensuite.

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

Jouez avec la substitution de processus.

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

grep sont deux binaires qui ont la même sortie de mycommand_exec comme entrée spécifique au processus.

17
Nikhil Mulley

Si vous utilisez zsh, vous pouvez profiter de la puissance de la fonction MULTIOS, c'est-à-dire se débarrasser complètement de la commande tee:

uname >file1 >file2

va simplement écrire la sortie de uname dans deux fichiers différents: file1 et file2, ce qui équivaut à uname | tee file1 >file2

Redirection similaire des entrées standard

wc -l <file1 <file2

est équivalent à cat file1 file2 | wc -l (veuillez noter que ce n'est pas la même chose que wc -l file1 file2, la dernière compte séparément le nombre de lignes de chaque fichier).

Bien sûr, vous pouvez également utiliser MULTIOS pour rediriger la sortie non pas 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/')
17
jimmij

Pour une sortie raisonnablement petite produite par une commande, nous pouvons rediriger la sortie vers un fichier temporaire et envoyer ces fichiers temporaires aux commandes en boucle. Cela peut être utile lorsque l'ordre des commandes exécutées peut être 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/sh as dash Shell:

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

Capturez la commande STDOUT dans 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 devez également capturer STDERR, utilisez 2>&1 à la fin de la commande, comme ceci:

commandoutput="$(command-to-run 2>&1)"
5
laebshade

Il y a aussi pee du paquet moreutils . Il est conçu pour cela: pee 'command1' 'command2' 'cat -'

2
Xorax

Cela peut être utile: http://www.spinellis.gr/sw/dgsh/ (shell graphique orienté) On dirait un remplacement bash supportant une syntaxe plus facile pour les commandes "multipipe".

1
sivann

Voici une solution partielle rapide et sale, compatible avec n'importe quel shell, y compris busybox.

Le problème le plus étroit qu'il résout est: imprimer le stdout complet sur une console et le filtrer sur une autre, sans fichiers temporaires ni canaux nommés.

  • Démarrez une autre session sur le même hôte. Pour connaître son nom TTY, tapez tty. Assumons /dev/pty/2.
  • Dans la première session, exécutez the_program | tee /dev/pty/2 | grep ImportantLog:

Vous obtenez un journal complet et un journal filtré.

0
Victor Sergienko