【问题标题】:How to get the PID of a process in a pipeline如何获取管道中进程的PID
【发布时间】:2010-07-27 15:52:58
【问题描述】:

考虑以下简化示例:

my_prog|awk '...' > output.csv & my_pid="$!" #Gives the PID for awk instead of for my_prog sleep 10 kill $my_pid #my_prog still has data in its buffer that awk never saw. Data is lost!

在 bash 中,$my_pid 指向 awk 的 PID。但是,我需要my_prog 的 PID。如果我杀死awkmy_prog 不知道刷新它的输出缓冲区并且数据丢失。那么,如何获得my_prog 的PID?请注意,ps aux|grep my_prog 将不起作用,因为可能有多个 my_prog 正在运行。

注意:将 cat 更改为 awk '...' 以帮助澄清我的需求。

【问题讨论】:

  • 我并没有真正通过管道连接到 cat,这只是一个简化的示例。这确实是一个丑陋的 awk 脚本,但它们的行为方式相同。
  • 你想完成什么?我相信一定有更好的方法。
  • 我有一个程序 my_prog,它可以生成大量数据。我使用 awk 脚本将数据汇总到 CSV 文件中,该文件将作为报告的基础。该程序实际上输出数据很好,直到我开始管道它。我相信这与 C 的“setbuf”特性有关,它将终端视为行缓冲,文件视为块缓冲(在这一点上我可能错了)。但也许如果我可以让程序在与 awk 对话时误以为它正在向终端写入数据,那可能会奏效。如果 my_prog 在退出时刷新它的缓冲区,如果我能得到 PID,那就更容易了。
  • 我更新了问题以澄清。谢谢你的慰问。也许有更简单的方法。

标签: bash awk


【解决方案1】:

刚刚遇到同样的问题。我的解决方案:

process_1 | process_2 &
PID_OF_PROCESS_2=$!
PID_OF_PROCESS_1=`jobs -p`

只要确保 process_1 是第一个后台进程即可。否则需要解析jobs -l的完整输出。

【讨论】:

  • 如果 make jobs -l 可以这样解析。稍后制作:PID_OF_PROCESS_1=jobs -l | grep process_1 | cut -f2 -d" "
【解决方案2】:

我能够通过使用mkfifo 明确命名管道来解决它。

第 1 步:mkfifo capture

第 2 步:运行此脚本

my_prog > capture & my_pid="$!" #Now, I have the PID for my_prog! awk '...' capture > out.csv & sleep 10 kill $my_pid #kill my_prog wait #wait for awk to finish.

我不喜欢拥有 mkfifo 的管理。希望有人有更简单的解决方案。

【讨论】:

  • 你为什么要杀死一个你想要输出的进程?
  • 进程是一个硬件监控程序,会一直运行到被杀死。当进程收到终止信号时,它会刷新其缓冲区。实际上,当测试结束时,bash 脚本将杀死 my_prog,这由上面的 sleep 语句表示。
【解决方案3】:

这是一个没有包装或临时文件的解决方案。这仅适用于后台管道,其输出是从包含脚本的标准输出中捕获的,就像您的情况一样。假设你想做:

cmd1 | cmd2 | cmd3 >pipe_out &
# do something with PID of cmd2

如果只有 bash 可以提供${PIPEPID[n]}!我发现的替换“hack”如下:

PID=$( { cmd1 | { cmd2 0<&4 & echo $! >&3 ; } 4<&0 | cmd3 >pipe_out & } 3>&1 | head -1 )

如果需要,您还可以分别使用3&gt;&amp;-4&lt;&amp;- 关闭fd 3(用于cmd*)和fd 4(用于cmd2)。如果您这样做,对于cmd2,请确保仅在 之后关闭 fd 4,然后从其中重定向 fd 0。

【讨论】:

    【解决方案4】:

    在您的命令周围添加一个外壳包装器并捕获 pid。对于我的示例,我使用 iostat。

    #!/bin/sh
    echo $$ > /tmp/my.pid
    exec iostat 1
    

    Exec 用保留 pid 的新进程替换 shell。

    test.sh | grep avg
    

    运行时:

    $ cat my.pid 
    22754
    $ ps -ef | grep iostat
    userid  22754  4058  0 12:33 pts/12   00:00:00 iostat 1
    

    所以你可以:

    sleep 10
    kill `cat my.pid`
    

    这样更优雅吗?

    【讨论】:

    • 不,包装器是不必要的复杂。还有其他几种解决方案
    【解决方案5】:

    使用单行器改进@Marvin@Nils Goroll 的答案,将管道中所有命令的pid 提取到shell 数组变量中:

    # run some command
    ls -l | rev | sort > /dev/null &
    
    # collect pids
    pids=(`jobs -l % | egrep -o '^(\[[0-9]+\]\+|    ) [ 0-9]{5} ' | sed -e 's/^[^ ]* \+//' -e 's! $!!'`)
    
    # use them for something
    echo pid of ls -l: ${pids[0]}
    echo pid of rev: ${pids[1]}
    echo pid of sort: ${pids[2]}
    echo pid of first command e.g. ls -l: $pids
    echo pid of last command e.g. sort: ${pids[-1]}
    
    # wait for last command in pipe to finish
    wait ${pids[-1]}
    

    在我的解决方案${pids[-1]} 中包含$! 中通常可用的值。请注意jobs -l % 的使用,它只输出“当前”作业,默认情况下是最后一个开始的作业。

    样本输出:

    pid of ls -l: 2725
    pid of rev: 2726
    pid of sort: 2727
    pid of first command e.g. ls -l: 2725
    pid of last command e.g. sort: 2727
    

    2017-11-13 更新:改进了pids=... 命令,使其更适用于复杂(多行)命令。

    【讨论】:

      【解决方案6】:

      根据您的评论,我仍然不明白为什么您宁愿杀死 my_prog 也不愿让它以有序的方式完成。在多处理系统上,10 秒是一个相当随意的测量值,my_prog 可以根据系统负载生成 10k 行或 0 行输出。

      如果您想将my_prog 的输出限制为更确定的尝试

      my_prog | head -1000 | awk
      

      不脱离外壳。在最坏的情况下,head 将关闭其输入并且 my_prog 将获得一个 SIGPIPE。在最好的情况下,更改 my_prog 以便它为您提供所需的输出量。

      针对评论添加

      只要您可以控制my_prog,就给它一个可选的-s duration 参数。然后在你的主循环的某个地方你可以放置谓词:

      if (duration_exceeded()) {
          exit(0);
      }
      

      exit 将依次正确刷新输出文件。如果绝望并且没有放置谓词的地方,可以使用 alarm(3) 来实现,我故意不显示它,因为它很糟糕。

      你的问题的核心是my_prog 永远运行。这里的所有其他内容都是绕过该限制的技巧。

      【讨论】:

      • 在我的回答中查看我的评论。我想我本可以就原始问题提供更多详细信息。上面的解决方案可能适用于某些人,但这种情况有点不同。感谢您迄今为止的所有帮助。我希望你能告诉我一个比我的答案更简单的解决方案。
      【解决方案7】:

      从@Demosthenex 的回答中获得灵感:使用子shell:

      $ ( echo $BASHPID > pid1; exec vmstat 1 5 ) | tail -1 & 
      [1] 17371
      $ cat pid1
      17370
      $ pgrep -fl vmstat
      17370 vmstat 1 5
      

      【讨论】:

        【解决方案8】:

        我一直在拼命寻找从管道作业中获取所有 PID 的好解决方案,但一种有前途的方法惨遭失败(请参阅此答案的先前修订版)。

        所以,不幸的是,我能想到的最好办法是使用 GNU awk 解析 jobs -l 输出:

        function last_job_pids {
            if [[ -z "${1}" ]] ; then
                return
            fi
        
            jobs -l | awk '
                /^\[/ { delete pids; pids[$2]=$2; seen=1; next; }
                // { if (seen) { pids[$1]=$1; } }
                END { for (p in pids) print p; }'
        }
        

        【讨论】:

          【解决方案9】:

          我的解决方案是查询 jobs 并使用 perl 解析它。
          在后台启动两条管道:

          $ sleep 600 | sleep 600 |sleep 600 |sleep 600 |sleep 600 &
          $ sleep 600 | sleep 600 |sleep 600 |sleep 600 |sleep 600 &
          

          查询后台作业:

          $ jobs
          [1]-  Running                 sleep 600 | sleep 600 | sleep 600 | sleep 600 | sleep 600 &
          [2]+  Running                 sleep 600 | sleep 600 | sleep 600 | sleep 600 | sleep 600 &
          
          $ jobs -l
          [1]-  6108 Running                 sleep 600
                6109                       | sleep 600
                6110                       | sleep 600
                6111                       | sleep 600
                6112                       | sleep 600 &
          [2]+  6114 Running                 sleep 600
                6115                       | sleep 600
                6116                       | sleep 600
                6117                       | sleep 600
                6118                       | sleep 600 &
          

          解析第二个作业%2的作业列表。解析可能容易出错,但在这些情况下它可以工作。我们的目标是捕获第一个数字,后跟一个空格。它使用括号作为数组存储到变量pids中:

          $ pids=($(jobs -l %2 | perl -pe '/(\d+) /; $_=$1 . "\n"'))
          $ echo $pids
          6114
          $ echo ${pids[*]}
          6114 6115 6116 6117 6118
          $ echo ${pids[2]}
          6116
          $ echo ${pids[4]}
          6118
          

          对于第一个管道:

          $ pids=($(jobs -l %1 | perl -pe '/(\d+) /; $_=$1 . "\n"'))
          $ echo ${pids[2]}
          6110
          $ echo ${pids[4]}
          6112
          

          我们可以把它包装成一个小别名/函数:

          function pipeid() { jobs -l ${1:-%%} | perl -pe '/(\d+) /; $_=$1 . "\n"'; }
          $ pids=($(pipeid))     # PIDs of last job
          $ pids=($(pipeid %1))  # PIDs of first job
          

          我在bashzsh 中对此进行了测试。不幸的是,在bash 中,我无法将 pipeid 的输出通过管道传输到另一个命令中。可能是因为该管道在无法查询作业列表的子 shell 中运行??

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2010-12-11
            • 2011-07-22
            • 2016-02-01
            • 1970-01-01
            • 2014-05-12
            • 1970-01-01
            • 1970-01-01
            • 2015-02-16
            相关资源
            最近更新 更多