dot*_*hen 81 command-line bash
假设一个简单的grep,例如:
$ psa aux | grep someApp
1000 11634 51.2 0.1 32824 9112 pts/1 SN+ 13:24 7:49 someApp
Run Code Online (Sandbox Code Playgroud)
这提供了很多信息,但由于缺少 ps 命令的第一行,因此没有信息的上下文。我更希望显示 ps 的第一行:
$ psa aux | someMagic someApp
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
1000 11634 51.2 0.1 32824 9112 pts/1 SN+ 13:24 7:49 someApp
Run Code Online (Sandbox Code Playgroud)
当然,我可以为 ps 专门为 grep 添加一个正则表达式:
$ ps aux | grep -E "COMMAND|someApp"
Run Code Online (Sandbox Code Playgroud)
但是,我更喜欢更通用的解决方案,因为在其他情况下我也希望拥有第一行。
似乎这将是“stdmeta”文件描述符的一个很好的用例。
Krz*_*ski 70
通常你不能用 grep 做到这一点,但你可以使用其他工具。已经提到了 AWK,但您也可以使用sed
,如下所示:
sed -e '1p' -e '/youpattern/!d'
Run Code Online (Sandbox Code Playgroud)
Sed 实用程序在每一行上单独工作,在每行上运行指定的命令。您可以有多个命令,指定多个-e
选项。我们可以在每个命令前面加上一个范围参数,该参数指定该命令是否应应用于特定行。
“1p”是第一个命令。它使用p
通常打印所有行的命令。但是我们在它前面加上一个数值,指定它应该应用到的范围。在这里,我们使用1
which 表示第一行。如果要打印更多行,可以使用x,yp
where x
is first line to print, y
is last line to print。例如要打印前 3 行,您可以使用1,3p
下一个命令d
通常是从缓冲区中删除所有行。在此命令之前,我们将放在yourpattern
两个/
字符之间。这是p
命令应该运行的寻址行的另一种方式(首先是指定哪些行,就像我们对命令所做的那样)。这意味着该命令仅适用于匹配的行yourpattern
。除了,我们!
在d
命令之前使用字符来反转其逻辑。所以现在它将删除所有与指定模式不匹配的行。
最后, sed 将打印缓冲区中剩余的所有行。但是我们从缓冲区中删除了不匹配的行,因此只会打印匹配的行。
总结一下:我们打印第一行,然后我们从输入中删除所有与我们的模式不匹配的行。行的其余都是印刷(即所以只有线做匹配图案)。
正如评论中提到的,这种方法存在问题。如果指定的模式也匹配第一行,它将被打印两次(一次通过p
命令,一次因为匹配)。我们可以通过两种方式避免这种情况:
后添加1d
命令1p
。正如我已经提到的,d
命令从缓冲区中删除行,我们用数字 1 指定它的范围,这意味着它只会删除第一行。所以命令是sed -e '1p' -e '1d' -e '/youpattern/!d'
使用1b
命令,而不是1p
. 这是一个伎俩。b
command 允许我们跳转到由标签指定的其他命令(这样可以省略一些命令)。但是如果没有指定这个标签(如我们的例子),它只会跳转到命令的末尾,忽略我们行的其余命令。所以在我们的例子中,最后一个d
命令不会从缓冲区中删除这一行。
ps aux | sed -e '1b' -e '/syslog/!d'
Run Code Online (Sandbox Code Playgroud)
一些sed
实现可以通过使用分号来分隔命令而不是使用多个-e
选项来为您节省一些输入。因此,如果您不关心便携性,则命令将是ps aux | sed '1b;/syslog/!d'
. 它至少适用于GNU sed
和busybox
实现。
但是,这是使用 grep 执行此操作的相当疯狂的方法。这绝对不是最佳的,我发布这个只是为了学习目的,但你可以使用它,例如,如果你的系统中没有任何其他工具:
ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog'
Run Code Online (Sandbox Code Playgroud)
首先,我们使用-n
选项在每行之前添加行号。我们想计算我们匹配的所有行.*
——任何行,甚至是空行。正如评论中所建议的,我们也可以匹配 '^',结果是一样的。
然后我们使用扩展的正则表达式,所以我们可以使用\|
作为 OR 的特殊字符。因此,如果该行以1:
(第一行)开头或包含我们的模式(在本例中为syslog
),我们将进行匹配。
现在的问题是,我们在输出中得到了这个丑陋的行号。如果这是一个问题,我们可以使用 删除它们cut
,如下所示:
ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog' | cut -d ':' -f2-
Run Code Online (Sandbox Code Playgroud)
-d
选项指定分隔符,-f
指定我们要打印的字段(或列)。所以我们想剪切每个:
字符的每一行,只打印第二列和所有后续列。这有效地删除了带有分隔符的第一列,这正是我们所需要的。
mrb*_*mrb 59
你感觉如何使用awk
而不是grep
?
chopper:~> ps aux | awk 'NR == 1 || /syslogd/'
USER PID %CPU %MEM VSZ RSS TT STAT STARTED TIME COMMAND
root 19 0.0 0.0 2518684 1160 ?? Ss 26Aug12 1:00.22 /usr/sbin/syslogd
mrb 574 0.0 0.0 2432852 696 s006 R+ 8:04am 0:00.00 awk NR == 1 || /syslogd/
Run Code Online (Sandbox Code Playgroud)
NR == 1
: 记录数 == 1; IE。第一行||
: 或者:/syslogd/
: 要搜索的模式也可能值得一看pgrep
,尽管这更适用于脚本而不是面向用户的输出。不过,它确实避免了grep
命令本身出现在输出中。
chopper:~> pgrep -l syslogd
19 syslogd
Run Code Online (Sandbox Code Playgroud)
Nah*_*eul 31
ps aux | { read line;echo "$line";grep someApp;}
Run Code Online (Sandbox Code Playgroud)
编辑:评论后
ps aux | { head -1;grep someApp;}
Run Code Online (Sandbox Code Playgroud)
我虽然head -1
会读取所有输入,但经过测试,它也可以工作。
{ head -1;grep ok;} <<END
this is a test
this line should be ok
not this one
END
Run Code Online (Sandbox Code Playgroud)
输出是
this is a test
this line should be ok
Run Code Online (Sandbox Code Playgroud)
我倾向于将标头发送到stderr:
ps | (IFS= read -r HEADER; echo "$HEADER" >&2; cat) | grep ps
Run Code Online (Sandbox Code Playgroud)
这通常足以满足人类阅读的目的。例如:
PID TTY TIME CMD
4738 pts/0 00:00:00 ps
Run Code Online (Sandbox Code Playgroud)
括号中的部分可以放入自己的脚本中以供一般使用。
还有一个额外的便利是输出可以进一步通过管道传输(到sort
等)并且标题将保留在顶部。
您还可以使用tee
和head
:
ps aux | tee >(head -n1) | grep syslog
Run Code Online (Sandbox Code Playgroud)
但是请注意,只要tee
无法忽略SIGPIPE
信号(例如参见此处的讨论),此方法就需要一种可靠的解决方法。解决方法是忽略 SIGPIPE 信号,例如,这可以在类似 shell 的 bash 中完成:
trap '' PIPE # ignore SIGPIPE
ps aux | tee >(head -n1) 2> /dev/null | grep syslog
trap - PIPE # restore SIGPIPE handling
Run Code Online (Sandbox Code Playgroud)
另请注意,不保证输出顺序。