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

Posted

技术标签:

【中文标题】使用 Bash 按列拆分命令输出?【英文标题】:Split output of command by columns using Bash? 【发布时间】:2010-12-10 10:28:59 【问题描述】:

我想这样做:

    运行命令 捕获输出 选择一行 选择该行的一列

仅作为示例,假设我想从$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 在第 2 列和第 3 列之间添加了一些空格以保持表格的相似性,cut 选择了一个空字符串。当然,我可以使用cut 选择第 7 个字段而不是第 4 个字段,但我怎么知道,特别是当输出是可变的且事先未知时。

【问题讨论】:

使用 awk(还有 25 个字符)。 【参考方案1】:

获取正确的行(例如第 6 行)是使用 head 和 tail 完成的,并且可以使用 awk 捕获正确的单词(单词 4):

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

【讨论】:

只是提醒未来的读者,awk 也可以按行选择:awk NR=6 print $4 会更有效 当然我的意思是awk NR==6 print $4 *doh*【参考方案2】:

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

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

【讨论】:

为了与原始问题兼容,ps | awk "\$1==$PIDprint\$4" 或(更好)ps | awk -v"PID=$PID" '$1=PIDprint$4'。当然,在 Linux 上你可以简单地使用 xargs -0n1 &lt;/proc/$PID/cmdline | head -n1readlink /proc/$PID/exe,但无论如何...... print $4; 中的; 是必需的吗?在 Linux 上删除它似乎对我没有影响,只是好奇它的目的 @igniteflow 如果您想在 print 语句之后继续添加,它不会表示命令结束吗?【参考方案3】:

一种简单的方法是添加@987654321@ 的传递以挤出任何重复的字段分隔符:

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

【讨论】:

我喜欢这个,貌似trawk更轻量级 我倾向于同意,但这也可能是因为我没有学过 awk。 :) 如果您碰巧有一个 PID 包含您感兴趣的 PID 作为子字符串的进程,则将无法工作。 此外,如果某些 PID:s 在左侧填充了空格,而其他没有,则字段编号将关闭。【参考方案4】:

试试

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

【讨论】:

@flybywire -- 对于这个简单的例子来说可能有点矫枉过正,但如果您需要对选定的数据进行更复杂的处理,这个习惯用法非常好。 另外,请注意现在默认的脚本 shell 通常不是 bash。【参考方案5】:

我建议您使用 ps 更改输出格式的功能,而不是执行所有这些 grep 和其他操作。

ps -o cmd= -p 12345

你得到一个进程的命令行,指定了 pid,没有别的。

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

【讨论】:

flybywire 说他只是以 ps 为例,这个问题比这更笼统。【参考方案6】:

使用数组变量

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

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

【讨论】:

【参考方案7】:

请注意tr -s ' ' 选项不会删除任何单个前导空格。如果您的列是右对齐的(如ps pid)...

$ 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"echo "$c" 中的变量周围使用引号。 似乎每个管道块都在其自己的子外壳或进程中执行,并且您不能将任何变量返回到封闭块?虽然你可以在回显它之后获得它的输出。 var=$(....... | read a b c d; echo $c; )。这仅适用于单个(字符串),但在 Bash 中,您可以使用 ar=($var) 将其拆分为数组 @tripleee 我认为在这个过程的这个阶段这不是问题。你很快就会发现你是否需要它,如果它在某个时候中断,这是一个学习课。然后你知道为什么你必须使用那些双引号;-)。然后它不再是你从别人那里听到的东西。玩火! :D。 :p. 详细回答:D 这个答案对我来说太有帮助了,我不能不这么说。【参考方案8】:

你的命令

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

错过了 tr -s 以压缩空间,正如 unwind 在 his answer 中解释的那样。

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

ps | awk '/11383/ print $4'

这将打印包含 11383 的行中的第 4 列。如果你想让它匹配出现在行首的11383,那么你可以说ps | awk '/^11383/ print $4'

【讨论】:

【参考方案9】:

类似于 brianegge 的 awk 解决方案,这里是 Perl 等价物:

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

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

由于 Perl 从 0 而不是 1 开始计数,因此会打印字段 3

【讨论】:

感谢您的 perl 解决方案 -- 不知道 autosplit,并且仍然认为 perl 是结束其他工具的工具.. ;)。【参考方案10】:

Bash 的set 会将所有输出解析为位置参数。

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

【讨论】:

此方法仅在命令有单行输出时有用。不够通用。 这不是真的,所有输出都放在位置参数中,而不管行。前set $(sar -r 1 1)echo "$23" 我的观点是,当输出量很大并且有很多字段时,很难确定参数的位置。 awk 是最好的方法。 这只是另一种解决方案。 OP 可能不想为这个单一用例学习 awk 语言。标签确实声明 bash 而不是 awk

以上是关于使用 Bash 按列拆分命令输出?的主要内容,如果未能解决你的问题,请参考以下文章

如何避免作为 sql 查询输出的一部分返回的字符串值被拆分为 bash/shell 脚本中数组中的不同字段

SC2207 来自 subshel​​l 的 Bash 数组分配未按预期拆分

在bash中将循环的每次迭代的输出附加到相同的内容

如何使用 Bash 抑制命令的所有输出?

Python使用numpy函数hsplit水平(按列)拆分numpy数组(返回拆分后的numpy数组列表)实战:水平(按列)拆分二维numpy数组split函数水平(按列)拆分二维numpy数组

使用 bash 命令的输出(带管道)作为另一个命令的参数