Comment utiliser tee pour rediriger vers grep

13

Je n'ai pas beaucoup d'expérience dans l'utilisation du tee, donc j'espère que ce n'est pas très basique.

Après avoir consulté l'une des réponses à cette question, je suis tombé sur un comportement étrange tee.

Pour que je puisse sortir la première ligne et une ligne trouvée, je peux utiliser ceci:

ps aux | tee >(head -n1) | grep syslog
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4

Cependant, la première fois que j'ai exécuté ceci (en zsh), le résultat était dans le mauvais ordre, les en-têtes de colonne étaient en dessous des résultats grep (cela ne s'est toutefois pas reproduit), j'ai donc essayé de permuter les commandes:

ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

Seule la première ligne est imprimée, et rien d'autre! Puis-je utiliser tee pour rediriger vers grep, ou est-ce que je fais cela de manière incorrecte?

Comme je tapais cette question, la deuxième commande a réellement fonctionné une fois pour moi, je l'ai exécutée à nouveau cinq fois, puis de nouveau au résultat d'une ligne. Est-ce juste mon système? (J'utilise zsh dans tmux).

Enfin, pourquoi avec la première commande "grep syslog" n'est-il pas affiché comme résultat (il n'y a qu'un seul résultat)?

Pour le contrôle, voici le grep sans le tee

ps aux | grep syslog
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4
henry    2290  0.0  0.1  95220  3092 ?        Ssl  Sep07   3:12 /usr/bin/pulseaudio --start --log-target=syslog
henry   15924  0.0  0.0   3128   824 pts/4    S+   13:44   0:00 grep syslog

Mise à jour: Il semble que head provoque la troncature de toute la commande (comme indiqué dans la réponse ci-dessous), la commande ci-dessous renvoie maintenant ce qui suit:

ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
syslog     806
Rqomey
la source
Ce n'est pas une réponse directe à votre question, mais il serait beaucoup plus propre de simplement faire quelque chose comme ça ps aux | sed -n -e '1p' -e '/syslog/p'.
jw013
Je n'ai même jamais pensé à sed, je pense que cela peut être une réponse appropriée à la question connexe ici, mais je recherche en fait des informations sur le comportement incohérent de ces commandes!
Rqomey

Réponses:

19
$ ps aux | tee >(head -n1) | grep syslog
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND 
syslog     806  0.0  0.0  34600   824 ?        Sl   Sep07   0:00 rsyslogd -c4

Les commandes grepet headcommencent à peu près au même moment et reçoivent toutes les deux les mêmes données d'entrée à leur guise, mais généralement lorsque les données deviennent disponibles. Il y a certaines choses qui peuvent introduire la sortie «non synchronisée» qui retourne les lignes; par exemple:

  1. Les données multiplexées de sont teeeffectivement envoyées à un processus avant l'autre, en fonction principalement de la mise en œuvre de tee. Une teeimplémentation simple aura une readcertaine quantité d'entrée, puis writedeux fois: une fois pour stdout et une fois pour son argument. Cela signifie que l'une de ces destinations obtiendra les données en premier.

    Cependant, les tuyaux sont tous tamponnés. Il est probable que ces tampons soient d'une ligne chacun, mais ils peuvent être plus grands, ce qui peut amener l'une des commandes de réception à voir tout ce dont elle a besoin pour la sortie (c'est-à-dire la grepligne ped) avant que l'autre commande ( head) n'ait reçu de données à tout.

  2. Nonobstant ce qui précède, il est également possible que l'une de ces commandes reçoive les données mais ne puisse rien faire avec elles à temps, et que l'autre commande reçoive plus de données et les traite rapidement.

    Par exemple, même si headet grepsont envoyées les données une ligne à la fois, si elles headne savent pas comment les traiter (ou sont retardées par la planification du noyau), elles greppeuvent afficher leurs résultats avant headmême d'avoir la chance de le faire. Pour démontrer, essayez d'ajouter un délai: ps aux | tee >(sleep 1; head -n1) | grep syslogcela produira presque certainement la grepsortie en premier.

$ ps aux | tee >(grep syslog) | head -n1
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

Je crois que vous obtenez souvent une seule ligne ici, car headreçoit la première ligne d'entrée, puis ferme son stdin et quitte. Quand teevoit que sa sortie standard a été fermée, il ferme alors sa propre sortie standard pset sort. Cela pourrait dépendre de la mise en œuvre.

En effet, les seules données qui psarrive à envoyer est la première ligne ( sans aucun doute, car headon contrôle cela), et peut - être quelques autres lignes avant headet teefermer les descripteurs stdin.

L'incohérence avec l'apparition de la seconde ligne est introduite par le timing: headferme stdin, mais psenvoie toujours des données. Ces deux événements ne sont pas bien synchronisés, donc la ligne contenant a syslogencore une chance de passer à teel'argument (la grepcommande). Ceci est similaire aux explications ci-dessus.

Vous pouvez éviter complètement ce problème en utilisant des commandes qui attendent toutes les entrées avant de fermer stdin / quitter. Par exemple, utilisez awkau lieu de head, qui lira et traitera toutes ses lignes (même si elles ne provoquent aucune sortie):

ps aux | tee >(grep syslog) | awk 'NR == 1'

Mais notez que les lignes peuvent toujours apparaître dans le désordre, comme ci-dessus, ce qui peut être démontré par:

ps aux | tee >(grep syslog) | (sleep 1; awk 'NR == 1')

J'espère que ce n'était pas trop de détails, mais il y a beaucoup de choses simultanées qui interagissent les unes avec les autres. Des processus distincts s'exécutent simultanément sans aucune synchronisation, de sorte que leurs actions sur une exécution particulière peuvent varier; Parfois, cela aide à approfondir les processus sous-jacents pour expliquer pourquoi.

mrb
la source
1
Excellente réponse! J'ai en fait demandé parce que je m'intéresse aux processus sous-jacents. Quand les choses sont inconstantes, je trouve ça intéressant. Y aurait-il une meilleure façon de fonctionner ps aux | tee >(grep syslog) | head -n1qui arrêterait de headfermer stdout. Wow, cette commande a commencé à donner une sortie maintenant, mais comme cela se produirait conformément à votre réponse, elle semble être tronquéeUSER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND syslog 806
Rqomey
1
Vous pouvez utiliser quelque chose qui ne ferme pas stdin à la place de head. J'ai mis à jour la réponse avec cet exemple:ps aux | tee >(grep syslog) | awk 'NR == 1'
mrb
1
@KrzysztofAdamski, lorsque vous utilisez >(cmd), le shell crée un canal nommé et le transmet comme argument à la commande ( tee). Puis teeécrit sur stdout (canalisé vers awk) et aussi sur cet argument. C'est la même chose que mkfifo a_fifo ; grep ... a_fifodans un shell et ps | tee a_fifo | awk ...dans un autre.
mrb
1
@KrzysztofAdamski gnu.org/software/bash/manual/html_node/… - Try echo >(exit 0), qui fera écho à l'argument réel passé par le shell (dans mon cas, il devient /dev/fd/63). Cela devrait fonctionner de la même manière sur bash et zsh.
mrb
1
@mrb: c'est une fonctionnalité très intéressante que je ne connaissais pas auparavant, merci. Cela fonctionne d'une manière étrange dans bash, cependant, voir pastebin.com/xFgRcJdF . Malheureusement, je n'ai pas le temps d'enquêter sur cela maintenant, mais je le ferai demain.
Krzysztof Adamski
2

grep syslogn'est pas toujours affiché car cela dépend du timing. Lorsque vous utilisez un pipeline shell, vous exécutez des commandes presque simultanément. Mais l'élément clé ici est le mot "presque". Si pstermine l'analyse de tous les processus avant le lancement de grep, il ne sera pas sur la liste. Vous pouvez obtenir des résultats aléatoires en fonction de la charge du système, etc.

La même chose se produit avec votre tee-shirt. Il est exécuté en arrière-plan en sous-shell et il peut être déclenché avant ou après grep. C'est pourquoi l'ordre de sortie est incohérent.

Quant à la question tee, son comportement est assez étrange. C'est parce qu'il n'est pas utilisé de façon normale. Il est exécuté sans aucun argument, ce qui signifie qu'il doit simplement copier les données de son stdin vers stdout. Mais sa sortie standard est redirigée vers le sous-shell exécutant head (dans le premier cas) ou grep (2e cas). Mais il est également dirigé vers la commande suivante. Je pense que ce qui se passe dans ce cas dépend en fait de la mise en œuvre. Par exemple sur ma bash 4.2.28, rien n'est jamais écrit pour sous-shell stdin. Sur zsh, cela fonctionne de manière fiable comme vous le souhaitez (en imprimant à la fois la première ligne de ps et les lignes recherchées), chaque fois que j'essaye,

Krzysztof Adamski
la source
Cela explique une chose de toute façon, je suis surpris que le tee retarde le fonctionnement de grep dans une mesure notable!
Rqomey
0

Un peu hackish, mais voici ma solution, sous la forme d'une psgrep()fonction shell que j'utilise:

Redirigez la psligne d'en-tête sur STDERR, puis grepsur STDOUT, mais supprimez d'abord la grepcommande elle-même, pour éviter la ligne "bruit" qui se crée d' grepelle-même:

psgrep() { ps aux | tee >(head -1>&2) | grep -v " grep $@" | grep "$@" -i --color=auto; }
fnl
la source