Comment utiliser sed pour manipuler la sortie de streaming en continu?

13

Je prépare une présentation pour un public non technique. J'ai un programme fonctionnant en bash qui produit un flux continu de valeurs, dont certaines sont importantes. Je voudrais souligner les résultats importants tels qu'ils sont affichés afin que le public puisse se faire une idée de leur fréquence. Le problème est que je ne peux pas sedopérer sur un flux en cours d'exécution. Cela fonctionne bien si je mets les résultats dans un fichier, comme dans:

cat output.txt | sed "s/some text/some text bolded/"

Mais si j'essaye la même chose sur la sortie en cours d'exécution, comme ceci:

command | sed "s/some text/some text bolded/"

sedne fait rien. Des pensées?

Comme Lambert a été assez utile pour le souligner, mon affirmation selon laquelle sedcela ne fait rien était vague. Ce qui se passe, c'est que le programme génère stdout(je suis sûr qu'il n'écrit pas stderr) comme il le ferait normalement, même s'il est acheminé sed.

Le problème semble être que la commande appelle un deuxième programme, qui sort ensuite vers stdout. Il y a quelques lignes imprimées par le premier programme; je peux les modifier. Ensuite, il y a un flux de valeurs imprimées par le deuxième programme; je ne peux pas les modifier.

Les méthodes Perl et awk ne fonctionnent pas non plus.

P Jones
la source
5
Ça stdbuf -o0 command | sed "s/some text/some text bolded/"marche?
FloHimself
3
Avec «sed ne fait rien», vous voulez dire que la substitution n'est pas effectuée, ou n'avez-vous aucune sortie? La commande écrit peut-être vers stderr au lieu de stdout? Si vous souhaitez mettre en évidence quelque chose que vous pourriez utilisercommand|egrep 'some text|$'
Lambert
La commande écrit-elle sur stdout (et non sur stderr)?
Anthon
1
Étant donné que le texte apparaît plus d'une fois, vous devez ajouter une gsubstitution "globale", sinon seule la première occurrence sur une ligne sera remplacée:sed "s/old/new/g"
ph0t0nix
@ ph0t0nix Non, ce n'est pas le problème, mais cela aurait été un problème sur toute la ligne. Merci pour le conseil.
P Jones

Réponses:

12

Il est probable que la sortie de la commande soit mise en mémoire tampon. Lorsque la commande écrit sur un terminal, le tampon est vidé sur chaque nouvelle ligne, vous le voyez donc apparaître au taux attendu. Lorsque la commande écrit dans un canal, le tampon n'est vidé que lorsqu'il atteint quelques kilo-octets, il est donc très en retard. Ainsi est le comportement par défaut de la bibliothèque d'entrée / sortie standard.

Pour forcer la commande à ne pas mettre en buffet sa sortie, vous pouvez utiliser unbuffer(à partir de l'attente) ou stdbuf(à partir de GNU coreutils).

unbuffer command | sed …
stdbuf -o0 command | sed …
Gilles 'SO- arrête d'être méchant'
la source
stdbufn'a pas fonctionné (il a été mentionné précédemment, BTW), mais unbuffera fonctionné !! Tu n'as aucune idée de ton bonheur.
P Jones
Et merci pour l'explication concise. Très utile.
P Jones
sedlui-même utilise un tel tampon, (cf ChennyStar post) donc les exemples ici peuvent ne pas fonctionner car sedc'est le commandto unbuffer: cat /etc/passwd | unbuffer sedmais sedlui-même a une -uoption, donc greppourrait être plus approprié dans ces exemples. Merci beaucoup pour vos informations de base! Très bonne réponse!
math
4

sed a une option pour cela:

-u, - sans tampon

Ce qui charge des quantités minimales de données à partir des fichiers d'entrée et vide les tampons de sortie plus souvent. Voir man sedpour plus de détails.

ChennyStar
la source
1
GNU seduniquement (pas BSD sed), et je pense que cela n'empêchera toujours pas la mise en mémoire tampon de la commande au début du canal. Mais bon de le mentionner. :)
Wildcard
La page de manuel stdbuf indique que "Si COMMAND ajuste la mise en mémoire tampon de ses flux standard ('tee' le fait par exemple), cela remplacera les modifications correspondantes par 'stdbuf'." Il semble que sed puisse tomber dans cette catégorie, mais le drapeau '-u' le rendra utile dans une chaîne de tuyaux sans tampon.
MattSmith
2

J'utiliserais awk

  command | awk '/some important stuff/ { printf "%c[31m%s%c[0m\n",27,$0,27 ; next }
  { print ; } '

  • /some important stuff/ sélectionner une ligne importante, comme dans sed
  • printf "%c[31m%s%c[0m\n",27,$0,27 ; impression en rouge
    • utilisez 32,33 pour le vert, le jaune ...
    • $ 1, $ 2, peuvent être utilisés pour sélectionner un champ spécifique
  • les autres lignes sont imprimées telles quelles

le point clé est que cela commanddevrait vider les lignes, mais cela devrait être le cas si vous avez beaucoup de sortie.

Archemar
la source
Remarque: La question ne dit pas que la ligne entière doit être "en gras", mais simplement "du texte". Bien qu'il soit possible d'implémenter également cette fonctionnalité dans awk(en utilisant sub()ou gsub()), dans le cas de cette substitution primitive, sedc'est certainement l'outil approprié.
Janis
1

La façon Perl:

command | perl -pe 's/(stuff)/\x1b[1m$1\x1b[0m/g'

ou avec une sortie continue:

Un script bash pour la sortie cont:

#!/bin/bash
while [ true ]
do
   echo "Some stuff"
done

Testez avec:

./cont | perl -pe 's/(stuff)/\x1b[1m${1}\x1b[0m/g'
  • \x1b[1m - intensité audacieuse ou augmentée
  • ${1} - le backreferenze
  • \x1b[0m - réinitialiser tous les attributs

Production:

Quelques trucs
Quelques trucs
Quelques trucs
Quelques trucs

Plus de codes d'échappement ici .

UN B
la source