【问题标题】:How can I loop over the output of a shell command?如何遍历 shell 命令的输出?
【发布时间】:2016-03-10 21:34:58
【问题描述】:

我想编写一个循环遍历 shell 命令ps 的输出(可能是数组?)的脚本。

这是命令和输出:

$ ps -ewo pid,cmd,etime | grep python | grep -v grep | grep -v sh
 3089 python /var/www/atm_securit       37:02
17116 python /var/www/atm_securit       00:01
17119 python /var/www/atm_securit       00:01
17122 python /var/www/atm_securit       00:01
17125 python /var/www/atm_securit       00:00

将其转换为 bash 脚本(sn-p):

for tbl in $(ps -ewo pid,cmd,etime | grep python | grep -v grep | grep -v sh)
do
   echo $tbl
done

但是输出变成:

3089
python
/var/www/atm_securit
38:06
17438
python
/var/www/atm_securit
00:02
17448
python
/var/www/atm_securit
00:01

如何像在 shell 输出中一样循环遍历每一行,但在 bash 脚本中?

【问题讨论】:

    标签: linux bash shell


    【解决方案1】:

    如果您想逐行处理 shell 命令的结果,请不要 for 循环遍历它,除非您将 内部字段分隔符 $IFS 的值更改为 \n。这是因为这些行将获得 分词 的主题,这会导致您看到的实际结果。意思是如果你有一个这样的文件:

    foo bar
    hello world
    

    下面的for循环

    for i in $(cat file); do
        echo "$i"
    done
    

    给你:

    foo
    bar
    hello
    world
    

    即使您使用IFS='\n',这些行仍可能获得Filename expansion 的主题


    我建议改用while + read,因为read 逐行读取。

    此外,如果您正在搜索属于某个二进制文件的 pid,我会使用 pgrep。但是,由于 python 可能显示为不同的二进制文件,例如python2.7python3.4,我建议将-f 传递给pgrep,这使得它可以搜索整个命令行,而不仅仅是搜索名为python 的二进制文件。但这也会找到已经启动的进程,如cat foo.py。你被警告了!最后,您可以根据需要优化传递给pgrep 的正则表达式。

    例子:

    pgrep -f python | while read -r pid ; do
        echo "$pid"
    done
    

    或者如果您还想要进程名称:

    pgrep -af python | while read -r line ; do
        echo "$line"
    done
    

    如果您希望进程名称和 pid 在单独的变量中:

    pgrep -af python | while read -r pid cmd ; do
        echo "pid: $pid, cmd: $cmd"
    done
    

    你看,read 提供了一种灵活而稳定的方式来逐行处理命令行的输出。


    顺便说一句,如果您更喜欢 ps .. | grep 命令行而不是 pgrep,请使用以下循环:

    ps -ewo pid,etime,cmd | grep python | grep -v grep | grep -v sh \
      | while read -r pid etime cmd ; do
        echo "$pid $cmd $etime"
    done
    

    注意我是如何更改etimecmd 的顺序的。因此能够将可以包含空格的cmd 读取到单个变量中。这是有效的,因为read 会将行分解为变量,次数与您指定的变量一样多。该行的剩余部分(可能包括空格)将分配给命令行中指定的最后一个变量。

    【讨论】:

    • 我假设您的意思是“pgrep”而不是“准备”?
    • 哦,当然.......顺便说一句,如果您需要 etime,请在循环中使用 ps -p "$pid" -o etime。当然,您也可以使用 ps .. | grep 命令行,但仍将其传递给 while read ... ...
    • 即使你设置了IFS,命令替换的扩展仍然会受到路径名扩展的影响,所以for循环是完全错误的。
    • cmd | while read 方法可能存在一些问题,具体取决于循环内部的内容:while 循环在子 shell 中运行,因此它设置的变量等不会超过循环,如果循环中的任何内容从标准输入读取,它将消耗命令输出。如果这些引起麻烦,您可以改用while read -u3 -r ... done 3< <(cmd)。但是<( ... ) 构造不能移植到所有的shell,所以一定要以仅bash 的shebang 启动脚本(#!/bin/bashnot #!/bin/sh
    • @GordonDavisson 即使在 POSIX 中,这个问题也可以通过命名管道来解决,因为进程替换只是为管理提供了一种方便的语法。
    【解决方案2】:

    我发现你可以使用双引号来做到这一点:

    while read -r proc; do
         #do work
    done <<< "$(ps -ewo pid,cmd,etime | grep python | grep -v grep | grep -v sh)"
    

    这会将每一行而不是每个项目保存到数组中。

    【讨论】:

    • 这会将命令的整个输出放在数组的单个元素中。
    • @EtanReisner 谢谢你的收获。
    • 为什么是&lt;&lt;&lt; 而不是&lt;
    • @Roland Here&lt;&lt;&lt; vs &lt;&lt; vs &lt; 上是一个很棒的答案
    • 这在 Ubuntu 中给我一个重定向错误
    【解决方案3】:

    当在 bash 中使用 for 循环时,它默认将给定列表拆分为 whitespaces,这可以通过使用所谓的 内部字段分隔符 或简而言之 IFS 进行调整。

    IFS 用于分词的内部字段分隔符 扩展并使用 read 内置命令将行拆分为单词。 默认值为“”。

    对于您的示例,我们需要告诉 IFS 使用 new-lines 作为断点。

    IFS=$'\n'
    
    for tbl in $(ps -ewo pid,cmd,etime | grep python | grep -v grep | grep -v sh)
    do
       echo $tbl
    done
    

    此示例在我的机器上返回以下输出。

      668 /usr/bin/python /usr/bin/ud    03:05:54
    27892 python                            00:01
    

    【讨论】:

    • 命令替换仍受路径名扩展的影响。 Do it right,并使用 while 循环。
    【解决方案4】:

    这是另一个基于 bash 的解决方案,灵感来自 @Gordon Davisson 的评论。

    为此我们需要(至少 bash v1.13.5 (1992) 或更高版本),因为使用了 Process-Substitution2,3,4while read var; do { ... }; done &lt; &lt;(...); 等.

    #!/bin/bash
    while IFS= read -a oL ; do {  # reads single/one line
        echo "${oL}";  # prints that single/one line
    };
    done < <(ps -ewo pid,cmd,etime | grep python | grep -v grep | grep -v sh);
    unset oL;
    

    注意:您可以在&lt;(...) 中使用任何简单或复杂的命令/命令集,它可能有多个输出行。
    什么代码做什么功能显示here

    这是一种单线/单线方式:
    while IFS= read -a oL ; do { echo "${oL}"; }; done &lt; &lt;(ps -ewo pid,cmd,etime | grep python | grep -v grep | grep -v sh); unset oL;

    ( 由于 Process-Substitution 还不是 POSIX 的一部分,因此它在许多 POSIX 兼容 shell 或 bash-shell 的 POSIX shell 模式中不受支持。Process-Substitution 自 1992 年以来就存在于 bash 中(所以从现在起 28 年前/ 2020),& 存在于 ksh86(1985 年之前)1。所以 POSIX 应该包含它。)
    如果您或任何用户想在符合 POSIX 的 shell 中使用类似于 Process-Substitution 的东西(即:sh、ash、dash、pdksh/mksh 等),请查看NamedPipes

    【讨论】:

      猜你喜欢
      • 2012-10-28
      • 1970-01-01
      • 2013-08-21
      • 2021-01-02
      • 2013-12-26
      • 1970-01-01
      • 1970-01-01
      • 2013-05-23
      相关资源
      最近更新 更多