如何grep特定行_和_文件的第一行?

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)

这个怎么运作:

  1. Sed 实用程序在每一行上单独工作,在每行上运行指定的命令。您可以有多个命令,指定多个-e选项。我们可以在每个命令前面加上一个范围参数,该参数指定该命令是否应应用于特定行。

  2. “1p”是第一个命令。它使用p通常打印所有行的命令。但是我们在它前面加上一个数值,指定它应该应用到的范围。在这里,我们使用1which 表示第一行。如果要打印更多行,可以使用x,ypwhere xis first line to print, yis last line to print。例如要打印前 3 行,您可以使用1,3p

  3. 下一个命令d通常是从缓冲区中删除所有行。在此命令之前,我们将放在yourpattern两个/字符之间。这是p命令应该运行的寻址行的另一种方式(首先是指定哪些行,就像我们对命令所做的那样)。这意味着该命令仅适用于匹配的行yourpattern。除了,我们!d命令之前使用字符来反转其逻辑。所以现在它将删除所有与指定模式匹配的行。

  4. 最后, sed 将打印缓冲区中剩余的所有行。但是我们从缓冲区中删除了不匹配的行,因此只会打印匹配的行。

总结一下:我们打印第一行,然后我们从输入中删除所有与我们的模式不匹配的行。行的其余都是印刷(即所以只有线匹配图案)。

第一行问题

正如评论中提到的,这种方法存在问题。如果指定的模式也匹配第一行,它将被打印两次(一次通过p命令,一次因为匹配)。我们可以通过两种方式避免这种情况:

  1. 后添加1d命令1p。正如我已经提到的,d命令从缓冲区中删除行,我们用数字 1 指定它的范围,这意味着它只会删除第一行。所以命令是sed -e '1p' -e '1d' -e '/youpattern/!d'

  2. 使用1b命令,而不是1p. 这是一个伎俩。bcommand 允许我们跳转到由标签指定的其他命令(这样可以省略一些命令)。但是如果没有指定这个标签(如我们的例子),它只会跳转到命令的末尾,忽略我们行的其余命令。所以在我们的例子中,最后一个d命令不会从缓冲区中删除这一行。

完整示例:

ps aux | sed -e '1b' -e '/syslog/!d'
Run Code Online (Sandbox Code Playgroud)

使用分号

一些sed实现可以通过使用分号来分隔命令而不是使用多个-e选项来为您节省一些输入。因此,如果您不关心便携性,则命令将是ps aux | sed '1b;/syslog/!d'. 它至少适用于GNU sedbusybox实现。

疯狂的方式

但是,这是使用 grep 执行此操作的相当疯狂的方法。这绝对不是最佳的,我发布这个只是为了学习目的,但你可以使用它,例如,如果你的系统中没有任何其他工具:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog'
Run Code Online (Sandbox Code Playgroud)

这个怎么运作

  1. 首先,我们使用-n选项在每行之前添加行号。我们想计算我们匹配的所有行.*——任何行,甚至是空行。正如评论中所建议的,我们也可以匹配 '^',结果是一样的。

  2. 然后我们使用扩展的正则表达式,所以我们可以使用\|作为 OR 的特殊字符。因此,如果该行以1:(第一行)开头或包含我们的模式(在本例中为syslog),我们将进行匹配。

行号问题

现在的问题是,我们在输出中得到了这个丑陋的行号。如果这是一个问题,我们可以使用 删除它们cut,如下所示:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog' | cut -d ':' -f2-
Run Code Online (Sandbox Code Playgroud)

-d选项指定分隔符,-f指定我们要打印的字段(或列)。所以我们想剪切每个:字符的每一行,只打印第二列和所有后续列。这有效地删除了带有分隔符的第一列,这正是我们所需要的。

  • 行编号也可以使用`cat -n` 来完成,并且看起来更清晰,就像为此滥用grep 一样。 (4认同)
  • 非常有教育意义的写得很好的答案。我试图为你用“Prepend”替换“Pretend”(接近开头),但它想要更多的变化,我不想改变你帖子中的随机废话,所以你可能想要解决这个问题。 (2认同)
  • `ps辅助| 如果第一行与 _pattern_ 匹配,sed '1p;/pattern/!d'` 将打印两次第一行。最好使用`b` 命令:`ps aux | sed -e 1b -e '/pattern/!d'`。`cat -n` 不是 POSIX。`grep -n '^'` 会对每一行进行编号(对于没有空行的 ps 输出来说不是问题)。`nl -ba -d $'\n'` 每行编号。 (2认同)
  • 请注意,`1b;...` 不是可移植的,也不是 POSIX,在“b”之后不能有任何其他命令,因此您需要换行符或另一个 -e 表达式。 (2认同)

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)

  • 我只是使用 `head -1` 而不是 read/echo 组合。 (3认同)
  • 这就是直接在 bash 中阐述的想法。我想为此竖起大拇指。我只是可能使用 `{ IFS='' 读取行;... }` 以防标题以空格开头。 (2认同)
  • `head -n1` 更短,但似乎即使 POSIX 规范也没有规定允许读取多少输入,所以可能是 `read line; 毕竟 echo $line` 更便携。 (2认同)

dai*_*isy 14

ps支持内部过滤,

假设您正在寻找 bash 进程:

ps -C bash -f

将列出所有名为bash.


ant*_*tak 6

我倾向于将标头发送到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等)并且标题将保留在顶部。


Tho*_*hor 5

您还可以使用teehead

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)

另请注意,不保证输出顺序

  • 添加睡眠以避免并发问题始终是一种技巧。虽然这可能奏效,但这是朝着黑暗面迈出的一步。-1 为此。 (2认同)