如何在管道中获取进程的PID

时间:2010-07-27 15:52:58

标签: bash awk

考虑以下简化示例:


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 '...'以帮助澄清我需要的内容。

9 个答案:

答案 0 :(得分:8)

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

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

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

答案 1 :(得分:5)

我能够使用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的管理。希望有人有一个更容易的解决方案。

答案 2 :(得分:5)

这是一个没有包装器或临时文件的解决方案。这仅适用于背景管道,其输出从包含脚本的stdout捕获,如您的情况。假设您想要这样做:

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

如果只有bash可以提供${PIPEPID[n]} !!我找到的替代“黑客”如下:

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

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

答案 3 :(得分:4)

在命令周围添加shell包装并捕获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`

这样更优雅吗?

答案 4 :(得分:3)

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

Log

在我的解决方案onCreate中包含String name; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.main_menu); final SharedPreferences shared = getSharedPreferences("PrefName", Context.MODE_PRIVATE); final SharedPreferences.Editor editor = shared.edit(); final int Coins = shared.getInt("CoinsValue", 1000); final boolean game = (shared.getBoolean("game", true)); // 1. Instantiate an AlertDialog.Builder with its constructor AlertDialog.Builder builder = new AlertDialog.Builder(MainMenu.this); // 2. Chain together various setter methods to set the dialog characteristics builder.setMessage("Unlock a new set to have more fun") .setTitle("UNLOCK SET"); // Add the buttons builder.setPositiveButton("100 coins", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { if (Coins >= 100){ editor.putInt(name, false).apply(); Toast.makeText(MainMenu.this, "Set Unlocked", Toast.LENGTH_SHORT).show(); coinsValue = Coins - 100; editor.putInt("CoinsValue", coinsValue); editor.apply(); Log.e("Msg", "" + name + " value = " + deathparade); } } }); builder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { // User cancelled the dialog } }); // 3. Get the AlertDialog from create() final AlertDialog dialog = builder.create(); gridView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { switch (position){ case 9: Log.e("Msg", "Game value = " + game); if(game){ name = "game"; editor.putString("nameLock", "game"); editor.apply(); Log.e("Msg", "" + name); dialog.show(); } else { editor.putInt("ArrayValue", R.array.game); editor.putInt("DrawableValue", R.drawable.game_bg); editor.apply(); startActivity(new Intent(MainMenu.this, MainActivity.class)); } break; 中通常可用的值。请注意使用# 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]}

更新2017-11-13:改进了$!命令,该命令可以更好地处理复杂(多行)命令。

答案 5 :(得分:2)

根据您的评论,我仍然无法理解为什么您更愿意以有序的方式杀死my_prog。在多处理系统上,十秒是一个非常随意的测量,my_prog可以产生10k行或0行输出,具体取决于系统负载。

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

my_prog | head -1000 | awk

没有脱离shell。在最坏的情况下,head将关闭其输入,my_prog将获得SIGPIPE。在最好的情况下,请更改my_prog,以便为您提供所需的输出量。

在回复评论时添加

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

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

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

麻烦的核心是my_prog永远运行。这里的其他所有东西都是克服这种限制的黑客。

答案 6 :(得分:2)

灵感来自@ Demosthenex的答案:使用子壳:

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

答案 7 :(得分:0)

我拼命寻找好的解决方案,从管道工作中获取所有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; }'
}

答案 8 :(得分:0)

我的解决方案是查询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中运行而无法查询作业列表??