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


76

假设一个简单的grep如:

$ psa aux | grep someApp
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

这提供了很多信息,但是由于缺少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

当然,我可以为grep专门为ps添加一个正则表达式:

$ ps aux | grep -E "COMMAND|someApp"

但是,我希望有一个更通用的解决方案,因为在其他情况下,我也希望有第一行。

似乎对于“ stdmeta”文件描述符来说,这将是一个很好的用例。


9
这些答案所要求的复杂性表明,按可用性的标准衡量时,“做一件事情并做得很好”的Unix哲学有时会使我们失败:了解所有这些命令足以将其应用于这个常见问题(过滤过程信息)。并仍然看到列标签)显示了该方法的缺点:有时情况并非很清楚。这就是为什么之类的工具ack是如此有用,为什么perl飙升过去sedawk等受欢迎:它的部分总结成一个连贯的整体是非常重要的。
iconoclast 2012年

3
当然,对于此特定示例,您可以使用-C参数,ps而无需将其通过管道传递到grep。例如ps u -C someApp,甚至ps u -C app1 -C app2 -C app3
cas

1
@iconoclast:当然,Unixy解决方案将是一种工具,可以多路复用多行,每行通过不同的过滤器集进行过滤。Kinda ps aux | { head -1; grep foo; }由@Nahuel Fouilleul在下面提到的广义版本(他的解决方案可能是我可以在需要时当场回想的唯一解决方案)
Lie Ryan

@iconoclast:缺乏工具的使用经验和知识,以及工具真正擅长的用途似乎始终毫无用处。精通命令并不是可用性的标准,而是阅读高级手册和实践的标准。这些工具已经存在了数十年。它们可以很好地(干净)地配合在一起。
ЯрославРахматуллин

@ЯрославРахматуллин:我认为您可能完全误解了我说的话。(也许是因为英语不是您的母语?)“可用性”与UX(“用户体验”)而不是实用程序(或“有用性”)相关。指出简单的操作如此复杂会损害可用性,这与说这些工具无用一样。很显然,它们并不是没有用的。在他们的头脑中没有人会说他们是无用的。
iconoclast 2012年

Answers:


67

好办法

通常,您无法使用grep执行此操作,但可以使用其他工具。已经提到了AWK,但是您也可以使用sed,例如:

sed -e '1p' -e '/youpattern/!d'

这个怎么运作:

  1. sed实用程序分别在每一行上工作,并在每行上运行指定的命令。您可以有多个命令,并指定了多个-e选项。我们可以在每个命令之前添加一个range参数,该参数指定是否应将此命令应用于特定行。

  2. “ 1p”是第一个命令。它使用p通常会打印所有行的命令。但是我们在其前面添加了一个数值,该数值指定了应应用的范围。在这里,我们使用的1是第一行。如果要打印更多行,则可以使用x,ypwhere x是要打印的第一行,y是要打印的最后一行。例如,要打印前三行,您将使用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。这是一个把戏。b命令允许我们跳转到标签指定的其他命令(这样可以省略某些命令)。但是,如果未指定此标签(如我们的示例),它将仅跳转到命令的末尾,而忽略该行的其余命令。因此,在本例中,最后一条d命令不会从缓冲区中删除此行。

完整示例:

ps aux | sed -e '1b' -e '/syslog/!d'

使用分号

某些sed实现可以使用分号来分隔命令而不是使用多个-e选项,从而节省了一些键入操作。因此,如果您不关心可移植性,则命令为ps aux | sed '1b;/syslog/!d'。它至少在GNU sedbusybox实现中有效。

疯狂的方式

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

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog'

这个怎么运作

  1. 首先,我们使用-n选项在每行之前添加行号。我们想对我们匹配的所有行进行编号.*-任何东西,甚至是空行。如注释中所建议,我们也可以匹配“ ^”,结果是相同的。

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

行号问题

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

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

-d选项指定定界符,-f指定我们要打印的字段(或列)。因此,我们希望剪切每个:字符上的每一行并仅打印第二行和所有后续列。这将有效地删除带有分隔符的第一列,而这正是我们所需要的。


4
行编号也可以使用cat -n,并且看起来比使用grep时更清晰。
Alfe 2012年

1
nl不计算空行(但不打印行号),cat -n使用前面的空格格式化编号,完全grep -n .去除空行并添加冒号。所有人都有他们的...呃...功能;-)
Alfe 2012年

2
极富教育意义的书面答案。我尝试用“ Prepend”代替“ Pretend”(接近开头),但它需要更多更改,而且我不希望更改帖子中的随机废话,因此您可能要修复它。
Bill K

2
ps aux | sed '1p;/pattern/!d'如果匹配pattern,将打印第一行两次。最好是使用b命令:ps aux | sed -e 1b -e '/pattern/!d'cat -n不是POSIX。grep -n '^'会为每一行编号(对于没有空行的ps输出不是问题)。nl -ba -d $'\n'每行编号。
斯特凡Chazelas

2
请注意,1b;...它既不是可移植的,也不是POSIX,在“ b”之后不能有任何其他命令,因此您需要换行符或另一个-e表达式。
斯特凡Chazelas

58

您对使用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/
  • NR == 1:记录数== 1; 即。第一行
  • ||: 要么:
  • /syslogd/:要搜索的模式

也许也值得一看pgrep,尽管这更多是针对脚本而不是面向用户的输出。但是,它确实避免了grep命令本身出现在输出中。

chopper:~> pgrep -l syslogd
19 syslogd

很好,谢谢。这对于将来的扩展也是很好的脚本。
dotancohen 2012年

我需要学点知识。非常好。
user606723 2012年

30
ps aux | { read line;echo "$line";grep someApp;}

编辑:评论后

ps aux | { head -1;grep someApp;}

我虽然head -1会读取所有输入,但是在测试之后,它也可以工作。

{ head -1;grep ok;} <<END
this is a test
this line should be ok
not this one
END

输出是

this is a test
this line should be ok

2
这就是直接用bash阐明的想法。我想为此多谢。我只是{ IFS='' read line; ... }在标题以空格开头的情况下使用。
Alfe 2012年

这不正是直接攻击的问题。真好!
dotancohen 2012年

3
我只是使用head -1而不是读/回声组合。
chepner 2012年

1
好吧,它适用head -n1于我的bash。这可能是特定于实现的。在这种情况下,我的头不会读取整个输入,只能读取第一行,而其余的都放在输入缓冲区中。
Krzysztof Adamski 2012年

2
head -n1的长度更短,但即使POSIX规范对于允许读取的输入量多少也保持沉默,所以read line; echo $line毕竟毕竟更可移植。
chepner 2012年

14

PS支持内部滤波器,

假设您正在寻找bash流程:

ps -C bash -f

将列出所有名为的进程bash


谢谢,很高兴知道。但是,它将找不到以python开头的脚本。
dotancohen 2012年

6

我倾向于将标头发送到stderr

ps | (IFS= read -r HEADER; echo "$HEADER" >&2; cat) | grep ps

通常这对于人类阅读而言已经足够。例如:

  PID TTY          TIME CMD
 4738 pts/0    00:00:00 ps

方括号中的部分可以放入自己的脚本中以供一般使用。

还有一个额外的便利,那就是可以将输出进一步传递给(sort等等),并且标头将保留在顶部。


5

您还可以使用teehead

ps aux | tee >(head -n1) | grep syslog

但是请注意,只要tee无法忽略SIGPIPE信号(例如,请参见此处讨论),此方法就需要一种变通方法来保证可靠性。解决方法是忽略SIGPIPE信号,例如,可以像在bash这样的shell中这样做:

trap '' PIPE    # ignore SIGPIPE
ps aux | tee >(head -n1) 2> /dev/null | grep syslog
trap - PIPE     # restore SIGPIPE handling

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


我不会依靠它来工作,第一次运行它(zsh)时,它会在grep结果下面产生列标题。第二次很好。
Rqomey 2012年

1
我还没有看到,但是提高可靠性的一种方法是在grep:之前的管道中插入一个小的延迟| { sleep .5; cat }
雷神

2
增加睡眠以避免并发问题始终是黑客。尽管这可能有效,但这是向黑暗面迈出的一步。-1。
Alfe 2012年

1
我在尝试此答案时遇到了其他一些奇怪的问题,我设置了一个要检查
Rqomey 2012年

这是tee的有趣用法,但我发现它不可靠,通常只打印输出行,而不打印标题行。
dotancohen 2012年

4

也许两个ps命令最简单。

$ ps aux | head -1 && ps aux | grep someApp
USER             PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
100         3304   0.0  0.2  2466308   6476   ??  Ss    2Sep12   0:01.75 /usr/bin/someApp

2
我不喜欢这种解决方案,主要是因为情况可能会在第一次ps aux通话和第二次通话之间发生变化……如果您只希望该静态第一行,为什么不手动回声呢?
Shadur 2012年

1
这种情况下,两个呼叫之间的更改都不会被打扰。第一个仅提供标题,该标题将始终适合第二个的输出。
Alfe 2012年

2
我不明白为什么这被否决了,这当然是一个可行的选择。正在投票。
dotancohen 2012年

4

您可以将pidstat用于:

pidstat -C someApp
or
pidstat -p <PID>

例:

# pidstat -C java
Linux 3.0.26-0.7-default (hostname)    09/12/12        _x86_64_

13:41:21          PID    %usr %system  %guest    %CPU   CPU  Command
13:41:21         3671    0.07    0.02    0.00    0.09     1  java

进一步的信息:http : //linux.die.net/man/1/pidstat


谢谢,很高兴知道。但是,它将找不到以python开头的脚本。
dotancohen 2012年

4

将以下内容放入您的.bashrc文件中,或首先将其复制/粘贴到shell中以进行测试。

function psls { 
ps aux|head -1 && ps aux|grep "$1"|grep -v grep;
}

用法:psls [grep模式]

$ psls someApp
USER             PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root              21   0.0  0.0  2467312   1116   ??  Ss   Tue07PM   0:00.17 /sbin/someApp

确保来源您的.bashrc(或如果放置在.bash_profile中,则来源):

source ~/.bashrc

该功能甚至会在shell命令行上自动完成。如您在另一个答案中所述,您可以将第一行通过管道传输到文件,以将一个呼叫保存到ps。


1
很好,我已经使用这种功能很多年了。我称呼我的versionpsl,该版本仅调用一次,ps并且grep每次调用一次(并且不需要head)。
亚当·卡兹

3

排序,但标题行保持在顶部

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

像这样使用

$ ps aux | body grep 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

谢谢,其中一些答案讨论了该问题的一般情况。完善!
dotancohen 2012年

3

非常感谢comp.unix.shell中的Janis Papanagnou,我使用以下函数:

function grep1 {
    IFS= read -r header && printf "%s\n" "$header"; grep "$@"
}

这具有许多优点:

  • 适用于bash,zsh以及可能的ksh
  • 它是grep的直接替代品,因此您可以继续使用所需的任何标志:-i不区分大小写的匹配,-E扩展的正则表达式等。
  • 如果您要以编程方式确定是否实际匹配任何行,则始终产生与grep相同的退出代码
  • 如果输入为空,则不显示任何内容

用法示例:

$ ps -rcA | grep1 databases
  PID TTY           TIME CMD

$ ps -rcA | grep1 -i databases
  PID TTY           TIME CMD
62891 ??         0:00.33 com.apple.WebKit.Databases

2

另一种方式gnu ed

ed -s '!ps aux' <<< $'2,$v/PATTERN/d\n,p\nq\n'

或者,如果外壳支持进程替换:

printf '%s\n' '2,$v/PATTERN/d' ,p q | ed -s <(ps aux)

那是:

2,$v/PATTERN/d  - remove all lines not matching pattern (ignore the header)
,p              - print the remaining lines
q               - quit

更可移植,无需gnu '!' 替换或用shell替换-仅使用ed内置方法rr的输出填充ps aux到缓冲区中,然后删除范围中不匹配的行2,$并打印结果:

printf '%s\n' 'r !ps aux' '2,$v/PATTERN/d' ,p q | ed -s

而且由于sed接受的答案输出中的命令也匹配它们自己的行,并带有sed支持-f-和的shell,它们支持进程替换,所以我将运行:

printf '%s\n' '2,${' '/PATTERN/!d' '}' | sed -f - <(ps aux)

与前面的ed命令几乎一样。


1

Perl方式:

ps aux | perl -ne 'print if /pattern/ || $.==1'

方式比起来更容易阅读sed,而且速度更快,没有冒乱的线条的风险。



0

如果那仅适用于具有完整标题的grepping进程,我将扩展@mrb的建议:

$ ps -f -p $(pgrep bash)
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
nasha     2810  2771  0  2014 pts/6    Ss+    0:00 bash
...

pgrep bash | xargs ps -fp将获得相同的结果,但没有子shell。如果需要其他格式:

$ pgrep bash | xargs ps fo uid,pid,stime,cmd -p
  UID   PID STIME CMD
    0  3599  2014 -bash
 1000  3286  2014 /bin/bash
 ...

-2

如果您知道确切的行号,那么使用perl会很容易!如果要从文件获取第1行和第5行,请说/ etc / passwd:

perl -e 'while(<>){if(++$l~~[1,5]){print}}' < /etc/passwd

如果您还想获取其他行,只需将其编号添加到数组中即可。


1
谢谢。根据OP,我知道该行中的一些文本,但不知道行号。
dotancohen

当寻找与OP密切相关的用例时,这会在Google上弹出作为答案,因此在此值得注意。
Dagelf

1
如果是这样,那么我强烈建议您提出一个新问题,并以这个答案回答。在SE上回答您自己的问题非常好,尤其是在您提到的情况下。继续并在对OP的评论中链接到您的新问题。
dotancohen

有这样的问题,但它们目前未在Google上弹出。
Dagelf

Dagelf,最重要的是-您的答案在这里没有回答问题。@dotancohen是正确的-如果在查找与OP密切相关的用例时,它在Google上弹出作为答案,然后问一个单独的问题-详细说明与该用例密切相关的用例,然后回答。
don_crissti
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.