使用Bash按列划分命令输出?


87

我想做这个:

  1. 运行命令
  2. 捕获输出
  3. 选择一条线
  4. 选择该行的一列

举例来说,假设我想从中获取命令名称$PID(请注意,这只是一个例子,我并不是在建议这是从进程ID中获取命令名称的最简单方法-我的真正问题是另一个我无法控制其输出格式的命令)。

如果我跑步,ps我会得到:


  PID TTY          TIME CMD
11383 pts/1    00:00:00 bash
11771 pts/1    00:00:00 ps

现在我做的ps | egrep 11383,并得到

11383 pts/1    00:00:00 bash

下一步:ps | egrep 11383 | cut -d" " -f 4。输出为:

<absolutely nothing/>

问题是cut将输出剪切单个空格,并ps在第二和第三列之间添加一些空格以保持表的相似性,因此cut选择一个空字符串。当然,我可以cut选择第7个字段而不是第4个字段,但是我怎么知道,特别是当输出是可变的且事先未知时。


2
使用awk(以及另外25个字符)。
Michael Foukarakis 09年

Answers:


178

一种简单的方法是添加tr来压缩任何重复的字段分隔符:

$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4

1
我喜欢这个,看起来trawk
-flybywire

3
我倾向于同意,但这也可能是因为我还没有学过awk。:)
放松

如果您碰巧有一个包含PID的进程,而该进程包含您感兴趣的PID,则该进程将作为子处理。
大卫·格雷森

1
而且,如果某些PID:s在左侧填充空格,而其他PID:s不在左侧,则字段编号将关闭。
2015年

68

我认为最简单的方法是使用awk。例:

$ echo "11383 pts/1    00:00:00 bash" | awk '{ print $4; }'
bash

4
为了与原始问题兼容,ps | awk "\$1==$PID{print\$4}"或(更好)ps | awk -v"PID=$PID" '$1=PID{print$4}'。当然,在Linux上,您可以简单地执行xargs -0n1 </proc/$PID/cmdline | head -n1readlink /proc/$PID/exe,但是无论如何...
ephemient

;{ print $4; }需要的?删除它在Linux上对我似乎没有任何影响,只是对它的用途感到好奇
igniteflow

如果您想继续添加print语句,则@igniteflow是否表示命令结束?
joshmcode

16

请注意,该tr -s ' '选项不会删除任何单个前导空格。如果您的列右对齐(与pspid一样)...

$ ps h -o pid,user -C ssh,sshd | tr -s " "
 1543 root
19645 root
19731 root

如果是第一列,则切割将导致其中某些字段的空白行:

$ <previous command> | cut -d ' ' -f1

19645
19731

除非您在其前面加上空格,否则显然

$ <command> | sed -e "s/.*/ &/" | tr -s " "

现在,对于pid数字(不是名称)的这种特殊情况,有一个称为的函数pgrep

$ pgrep ssh


外壳功能

但是,通常,实际上仍然可以以简洁的方式使用shell函数,因为该read命令有一个巧妙的地方:

$ <command> | while read a b; do echo $a; done

要读取的第一个参数a选择第一列,如果还有更多列,则将其他所有内容都放入b。结果,您所需要的变量永远不会超过+1列的数量。

所以,

while read a b c d; do echo $c; done

然后将输出第三列。如我的评论所示...

管道读取将在不将变量传递给调用脚本的环境中执行。

out=$(ps whatever | { read a b c d; echo $c; })

arr=($(ps whatever | { read a b c d; echo $c $b; }))
echo ${arr[1]}     # will output 'b'`


阵列解决方案

因此,我们最后得到了@frayser的答案,这是使用默认为空格的shell变量IFS将字符串拆分为数组。它仅在Bash中有效。Dash和Ash不支持它。我在将字符串拆分成Busybox组件中的过程中确实遇到了困难。获得单个组件(例如使用awk),然后针对所需的每个参数重复该过程非常容易。但是随后您最终会在同一行上重复调用awk,或者在同一行上重复使用带有echo的读取块。这不是有效的还是漂亮的。所以你最终使用分裂 ${name%% *}等等。使您渴望一些Python技能,因为如果您习惯的一半或更多功能消失了,那么实际上,shell脚本就不再有趣了。但是您可以假设即使在这样的系统上也不会安装python,但不是;-)。


您应该在echo "$a"and变量中使用引号echo "$c"
三胞胎

似乎每个管道块都在其自己的子shell或进程中执行,并且您无法将任何变量返回到封闭的块中?尽管您可以在回显后获得该输出。var=$(....... | { read a b c d; echo $c; })。这仅适用于单个(字符串),尽管您可以在Bash中使用ar=($var)
Xennex81'3

@tripleee我认为在过程的这个阶段这不是问题。您将很快发现是否需要它,如果在某个时候出现问题,这是一个学习课程。然后,您知道了为什么必须使用那些双引号;-)。然后,您不再会听到别人说过的话。玩火!:D。:p。
Xennex81 2015年

详尽的答案:D
ncomputers'May

这对我来说是一个很有帮助的答案,所以不要这么说。
伊万X

4

尝试

ps |&
while read -p first second third fourth etc ; do
   if [[ $first == '11383' ]]
   then
       echo got: $fourth
   fi       
done

1
@flybywire-对于这个简单的示例来说可能会适得其反,但是如果您需要对所选数据进行更复杂的处理,那么这种用法非常有用。
James Anderson

另外,请注意,这些天默认的脚本外壳通常不是bash。
大卫

2

使用数组变量

set $(ps | egrep "^11383 "); echo $4

要么

A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}

2

与brianegge的awk解决方案类似,这是Perl的等效项:

ps | egrep 11383 | perl -lane 'print $F[3]'

-a启用自动拆分模式,该模式将@F使用列数据填充数组。
使用-F,如果你的数据是用逗号分隔的,而不是空格分隔。

由于Perl从0开始而不是从1开始计数,所以将打印字段3


1
谢谢您的perl解决方案-不了解自动拆分,并且 但仍然认为perl是结束其他工具的工具。
Gerard ONeill

1

用头和尾完成正确的行(第6行的示例),并且可以使用awk捕获正确的单词(第4个单词):

command|head -n 6|tail -n 1|awk '{print $4}'

只是向未来的读者指出,awk也可以按行进行选择:awk NR=6 {print $4}效率会更高一点
David Z

1
而且我当然是说awk NR==6 {print $4}* doh *
David Z

1

你的命令

ps | egrep 11383 | cut -d" " -f 4

错过了atr -s来挤压空间,如放松在他的回答中所述

但是,您可能要使用awk,因为它可以在一个命令中处理所有这些操作:

ps | awk '/11383/ {print $4}'

这将在包含的行中打印第4列11383。如果您希望此匹配项11383出现在行首,则可以说ps | awk '/^11383/ {print $4}'


0

建议您不要使用所有更改输出格式的ps功能,而不要做所有这些事情。

ps -o cmd= -p 12345

您将获得具有指定pid且没有其他任何内容的进程的cm​​mand行。

这符合POSIX,因此可以认为是可移植的。


1
flybywire指出他只是以ps为例,问题比这更笼统。
Ogre Psalm33'2013-04-30

0

Bashset将所有输出解析为位置参数。

例如,使用set $(free -h)命令echo $7将显示“ Mem:”


仅当命令具有一行输出时,此方法才有用。不够通用。
codeforester

这是不正确的,所有输出都放置在位置参数中,而与行无关。前set $(sar -r 1 1); echo "${23}"
dman

我的观点是,当输出量很大且包含许多字段时,很难确定参数的位置。 awk是最好的方法。
codeforester

这只是另一个解决方案。OP可能不想为此单个用例学习awk语言。标签会声明,bash而不是awk
dman
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.