5

假设我通过管道输出命令的输出并希望使用 grep 过滤行,但还要保留第一个作为标题的行。我看到有人键入类似这样的内容:

the command | (read l; echo$l) | grep bla | less

它提取了第一行(标题),然后将文件的其余部分用于匹配 bla 的行,并将其输出less用于检查。当然上面的命令不起作用,但这就是想法,它的哪一部分是错误的?

4

4 回答 4

6

使用 awk:

command | awk 'NR==1||/bla/'

感谢@doubleDown 指出这{print}是不必要的,因为它是默认操作。

使用 perl:

command | perl -ne 'print if $.==1 or /bla/'

(如果您需要 perl 不规则表达式,perl 可能可用 :) )

于 2013-06-14T17:16:12.667 回答
2

sed味道:

command | sed -ne '1p' -ne '/bla/p'
于 2013-06-14T18:01:10.213 回答
1

除了grep第一行之外的所有内容

以下内容大部分会得到你想要的,但它有几个缺陷(见本文末尾):

the command | (read l; echo $l; grep blah) | less

相反,我建议创建和使用以下函数:

grep1 () (
    IFS= read -r line
    printf %s\\n "${line}"
    grep "$@"
)

以下是您将如何使用它:

the command | grep1 blah | less

实际操作示例:

$ ps -ef | grep1 firefox
UID        PID  PPID  C STIME TTY          TIME CMD
rhansen   3654  3311  4 13:33 ?        00:07:59 /usr/lib/firefox/firefox

这个怎么运作

  1. read使用第一行输入command并将其(未修改)分配给变量line
  2. printf输出line(未修改)的值
  3. 剩余的输入线被消耗、过滤和输出grep

第一行永远不会通过grep,因此没有机会将其过滤掉。

笔记

  • 我将函数体包含在其中,( ... )而不是{ ... }因为我不希望函数体中的变量分配影响调用者的环境(括号导致它在子shell中运行,从而将任何更改与调用者隔离开来)。
  • IFS=防止read剥离前导和尾随空格
  • 阻止它处理反斜杠的-r参数read(第一行完全保留在变量中line
  • 我使用printf %s\\n而不是echo因为echo可能会处理反斜杠,可能导致输出的第一行与原始的第一行不同

改进

上面的函数有一个小问题:如果给定空输入,它将打印一个空行。以下内容避免了该问题:

grep1_better() (
    IFS= read -r line && printf %s\\n "${line}"
    grep "$@"
)

这是有效的,因为read如果遇到输入结束,则返回非零返回码。如果没有输入,read将“失败”(返回非零)并且&&将跳过printf.

但是,现在有一个新问题:如果有输入,但根本没有任何换行符(例如,printf %s foo),该函数将不输出任何内容。这是因为read即使有一些输入,也会遇到输入结束和“失败”。以下是可以解决的方法:

grep1_even_better() (
    IFS= read -r line || [ -n "${line}" ] && printf %s\\n "${line}"
    grep "$@"
)

在英语中,上面说:“读取一行输入。如果没有遇到输入的结尾,或者如果读取了某些内容,则打印读取的内容。然后运行grep。”

进一步的改进是检测何时使用一个或多个文件名参数调用函数并做出相应的反应(从文件而不是标准输入读取)。

这个例子有什么问题?

以下代码不起作用:

the command | (read l; echo $l) | grep bla | less

有两个主要问题:

  • 第一行仍然通过管道传输grep,因此grep仍然可以将其过滤掉。
  • 剩余的输入行被流水线的第二阶段丢弃。(更准确地说,“ the command”命令永远不会有机会输出剩余的行(模缓冲),因为第二阶段没有人在等待读取它们。)

此外,还有一些小问题:

  • 因为IFS在调用之前没有设置为空字符串readread所以在分配变量之前会去除第一行的前导和尾随空格l
  • 因为-r未传递给read,所以read将尝试解释第一输入行中的反斜杠。这可能会破坏第一行。
  • 因为 to 的参数echo没有用双引号括起来,所以制表符和多个连续的空格将被转换为单个空格。如果第一行包含列标题,这将破坏与以下行的对齐。
  • 因为echo可能会在其参数中处理反斜杠,所以第一行可能已损坏。
  • 如果第一行以 开头-echo可能会将字符串解释为选项,而不是要打印的内容。
  • 如果给定空输入,它将打印一个空行。

这些小问题也存在于 中the command | (read l; echo $l; grep blah) | less,这就是我推荐该grep1()功能的原因。

于 2013-06-14T20:54:27.370 回答
0

上面的awksed答案通常是要走的路。有时,当所讨论的正则表达式很复杂并且grep是唯一选项时,以下tee基于 - 的选项应该可以工作。这里tee将其输入写入两个“文件”,grephead通过进程替换使用输入。tee还将其输入写入标准输出,在这种情况下需要重定向到/dev/null. sleep需要确保在head之前返回其输出grep

command | tee  >(sleep 1; grep regex) >(head -1) >/dev/null
于 2013-06-14T18:21:40.527 回答