这个问题帮助我理解如何结合 stderr 和 stdout:
使用以下命令:
gunzip -vc /opt/minecraft/wonders/logs/20* 2>&1
但是如何在两者之间插入换行符,以便输出可以出现在不同的行上而不是聚集在一起?
stderr 首先出现,它始终只有 1 行,如下所示:
/opt/minecraft/wonders/logs/2017-08-28-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
但是因为它们之间没有换行符,所以每个要解压缩的文件的第一行总是这样的:
/opt/minecraft/wonders/logs/2018-08-18-2.log.gz: 06:17:05: Starting minecraft server version 1.10.2
我需要将这两部分放在不同的行上,这样我就可以用 grep 过滤它们。但我还希望相应的 stdout 直接跟在其 stderr(其文件名)后面,这样我就知道它来自哪个文件。像这样:
/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
这是我通过 grep 传递的命令:
egrep -iv "^\[.*(stopping|starting|saving|keep|spawn|joined|lost|left|: (<.*>|\/)|\/(help|forge))
其目的是从日志文件中删除某些行,保留相关的行。
答案1
这里有一个简单的方法可以做到这一点,假设这个命令:
ls -d Documents/ NotExist/ /dev/null
输出为:
ls: cannot access 'NotExist/': No such file or directory
/dev/null Documents/
第一行是stderr
,第二行是stdout
,如果我将输出传输到,xargs
我可以在之后打印一个新行stderr
,然后打印stdout
:
ls -d Documents/ NotExist/ /dev/null | xargs echo -e '\n'
这给了我:
ls: cannot access 'NotExist/': No such file or directory
/dev/null Documents/
答案2
复制文件描述符是可能的。长话短说:管道 stderr,可以在那里处理它,并将 stdin 发送到控制终端(简写为/dev/tty
),以便它仍然显示在屏幕上:
$ 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
---
这里我将 stderr 与---
line 分开,但你明白我的意思。
另一种方法是,如果你想在视觉上区分它们,那就是给输出着色。基于Balazs Pozar 的回答,你可以这样做:
command 2> >(while read line; do echo -e "\e[01;31m$line\e[0m" >&2; done)
为了解决这个问题的编辑,如果您需要使用 stdout 和 stderr 进行过滤grep
(例如,如果您在 中寻找一个模式stdout
,但在 中寻找另一个模式stderr
),则可以使用多个过程替换>()
:
gunzip -vc /opt/minecraft/wonders/logs/20* > >(grep 'stdin pattern' ) 2> >(grep 'stderrpattern' )
针对评论,如果您只需要将文件名/opt/minecraft/wonders/logs/2017-08-28-2.log.gz:
与进一步的内容分开,则可以使用sed
:
gunzip -vc /opt/minecraft/wonders/logs/20* |
sed -r 's/^(\/opt\/minecraft\/wonders\/logs\/2018-08-18-2.log.gz:) *(.*)$/\1\n\2/'
测试:
$ 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
答案3
我认为实际发生的情况是 stdout 正在混入stderr,因为前者默认是行缓冲的,而后者默认是非缓冲的。举例来说:
$ gunzip -vc ../foo.txt.gz
../foo.txt.gz: some text here
more text here
26.7%
(请注意,这-v
会导致在 stderr 上发出两条信息:文件名和 % 压缩 - 标准输出介于两者之间)。
如果您也对 stderr 进行行缓冲,则流将出现在单独的行上:
$ stdbuf -eL gunzip -vc ../foo.txt.gz
some text here
more text here
../foo.txt.gz: 26.7%
但不一定按照您需要的顺序 - 您可以尝试使用该sponge
实用程序来吸收全部在输出之前,stdout 会先保留,这样 stderr 输出(文件名和%)就会首先出现在终端中:
gunzip -vc ../foo.txt.gz | sponge
../foo.txt.gz: 26.7%
some text here
more text here