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

我想这样做:

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

【问题讨论】:

  • 使用 awk(还有 25 个字符)。

标签: linux bash pipe


【解决方案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==$PID{print\$4}" 或(更好)ps | awk -v"PID=$PID" '$1=PID{print$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
      猜你喜欢
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 2018-10-03
      • 1970-01-01
      • 2014-05-02
      • 2023-01-23
      • 2020-03-22
      • 1970-01-01
      相关资源
      最近更新 更多