web-dev-qa-db-fra.com

Comment insérer un saut de ligne entre stderr et stdout

Cette question m'a aidé à comprendre comment combiner stderr et stdout:

Comment rediriger stderr vers un fichier

avec cette commande:

gunzip -vc /opt/minecraft/wonders/logs/20* 2>&1

Mais comment peut-on insérer un saut de ligne entre les deux pour que la sortie apparaisse sur des lignes séparées au lieu d'être groupées?

Le stderr vient en premier, qui est toujours une ligne, quelque chose comme ceci:

/opt/minecraft/wonders/logs/2017-08-28-2.log.gz:

Alors que stdout est généralement (mais pas toujours) plusieurs lignes, comme:

06:17:05: Starting minecraft server version 1.10.2
06:18:21: Loading properties
06:18:21: Default game type: SURVIVAL
06:18:21: Generating keypair
06:18:21: Starting Minecraft server on *:25565
06:18:22: Using default channel type

Mais comme il n'y a pas de saut de ligne entre eux, la première ligne de chaque fichier à décompresser ressemble toujours à ceci:

/opt/minecraft/wonders/logs/2018-08-18-2.log.gz:    06:17:05: Starting minecraft server version 1.10.2

J'ai besoin de ces deux parties sur des lignes différentes pour pouvoir les filtrer avec grep. Mais je veux aussi que le stdout correspondant suive directement son stderr (son nom de fichier) afin que je sache de quel fichier il provient. Comme ça:

/opt/minecraft/wonders/logs/2018-08-18-2.log.gz:    
06:17:05: Starting minecraft server version 1.10.2
06:18:21: Loading properties
06:18:21: Default game type: SURVIVAL
06:18:21: Generating keypair
06:18:21: Starting Minecraft server on *:25565
06:18:22: Using default channel type

Voici la commande que je suis en train d'utiliser avec grep:

egrep -iv "^\[.*(stopping|starting|saving|keep|spawn|joined|lost|left|: (<.*>|\/)|\/(help|forge))

Son but est de supprimer certaines lignes des fichiers journaux tout en laissant celles qui sont pertinentes.

2
Jesse Yishai

Voici un moyen simple de faire cela, supposons cette commande:

ls -d Documents/ NotExist/ /dev/null

la sortie est:

ls: cannot access 'NotExist/': No such file or directory
/dev/null  Documents/

la première ligne est stderr et la seconde est stdout, si je dirige la sortie vers xargs je peux imprimer une nouvelle ligne après stderr, puis imprimer le stdout:

ls -d Documents/ NotExist/ /dev/null | xargs echo -e '\n'

ce qui me donne:

ls: cannot access 'NotExist/': No such file or directory

 /dev/null Documents/
2
Ravexina

C'est possible avec la duplication de descripteurs de fichiers. Récit long, bref: pipe stderr, où il peut être traité, et envoyez stdin au terminal de contrôle (pour abréger, il s'agit de /dev/tty), de sorte qu'il s'affiche toujours à l'écran:

$ stat /etc/passwd noexist 2>&1 > /dev/tty | while IFS= read -r line; do echo "---"; echo "$line"; echo "---"; done
  File: /etc/passwd
  Size: 2380        Blocks: 8          IO Block: 4096   regular file
Device: 801h/2049d  Inode: 937653      Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-08-20 13:05:01.171247259 +0800
Modify: 2018-07-24 04:51:36.799057737 +0800
Change: 2018-07-24 04:51:36.867020035 +0800
 Birth: -
---
stat: cannot stat 'noexist': No such file or directory
---

Ici, je sépare stderr avec la ligne ---, mais vous voyez l’idée.

Si vous souhaitez les séparer visuellement, vous pouvez également colorier le résultat. Basé sur réponse de Balazs Pozar , vous pourriez faire:

command 2> >(while read line; do echo -e "\e[01;31m$line\e[0m" >&2; done)

enter image description here


Pour résoudre le problème d'édition, si vous devez filtrer stdout et stderr avec grep (par exemple, si vous recherchez un modèle dans stdout, mais un autre modèle dans stderr), vous utilisez plusieurs substitutions de processus >():

gunzip -vc /opt/minecraft/wonders/logs/20* > >(grep 'stdin pattern' ) 2> >(grep 'stderrpattern' )

En adressant les commentaires, si vous avez seulement besoin de séparer le nom de fichier /opt/minecraft/wonders/logs/2017-08-28-2.log.gz: du contenu ultérieur, vous pouvez utiliser sed:

gunzip -vc /opt/minecraft/wonders/logs/20* | 
sed -r 's/^(\/opt\/minecraft\/wonders\/logs\/2018-08-18-2.log.gz:) *(.*)$/\1\n\2/'

Tester:

$ echo /opt/minecraft/wonders/logs/2018-08-18-2.log.gz:    06:17:05: Starting minecraft server version 1.10.2 | sed -r 's/^(\/opt\/minecraft\/wonders\/logs\/2018-08-18-2.log.gz:) *(.*)$/\1\n\2/'
/opt/minecraft/wonders/logs/2018-08-18-2.log.gz:
06:17:05: Starting minecraft server version 1.10.2
2
Sergiy Kolodyazhnyy

Je pense que ce qui se passe réellement, c’est que stdout est en train de devenir mélangé à stderr, parce que le premier est mis en tampon de ligne et le dernier est non tampon par défaut. Pour illustrer:

$ gunzip -vc ../foo.txt.gz
../foo.txt.gz:  some text here
more text here
 26.7%

(notez que -v provoque l’émission de deux informations sur stderr: le nom de fichier et le% compression - avec une sortie standard située entre les deux).

Si vous mettez également stderr en mémoire tampon, les flux apparaîtront sur des lignes distinctes:

$ stdbuf -eL gunzip -vc ../foo.txt.gz
some text here
more text here
../foo.txt.gz:   26.7%

mais pas nécessairement dans l'ordre requis - vous pouvez essayer d'utiliser l'utilitaire sponge pour absorber tous de stdout avant de le sortir, afin que la sortie stderr (nomfichier et%) apparaisse dans le terminal d'abord:

gunzip -vc ../foo.txt.gz | sponge
../foo.txt.gz:   26.7%
some text here
more text here
1
steeldriver