Как получить PID процесса в конвейере - PullRequest
14 голосов
/ 27 июля 2010

Рассмотрим следующий упрощенный пример:


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 указывает на PID для awk.Однако мне нужен PID для my_prog.Если я убиваю awk, my_prog не знает, как очистить его выходной буфер, и данные будут потеряны.Итак, как можно получить PID для my_prog?Обратите внимание, что ps aux|grep my_prog не будет работать, так как может быть несколько my_prog.

ПРИМЕЧАНИЕ: изменили cat на awk '...', чтобы уточнить, что мне нужно.

Ответы [ 9 ]

8 голосов
/ 29 сентября 2011

Просто была такая же проблема. Мое решение:

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

Просто убедитесь, что process_1 - это первый фоновый процесс. В противном случае вам нужно проанализировать полный вывод jobs -l.

5 голосов
/ 20 апреля 2011

Вот решение без упаковщиков или временных файлов.Это работает только для фонового конвейера, выходные данные которого выводятся из стандартного вывода содержащего сценария, как в вашем случае.Предположим, вы хотите сделать:

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 )

При необходимости вы также можете закрыть fd 3 (для cmd*) и fd 4 (для cmd2) с помощью 3>&-и 4<&- соответственно.Если вы сделаете это, для cmd2 убедитесь, что закрываете только fd 4 после вы перенаправляете с него fd 0.

5 голосов
/ 27 июля 2010

Я смог решить эту проблему с явным присвоением имени каналу, используя 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. Надеюсь, у кого-то есть более простое решение.

4 голосов
/ 27 июля 2010

Добавьте оболочку вокруг вашей команды и захватите pid. Для моего примера я использую iostat.

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

Exec заменяет оболочку новым процессом, сохраняющим pid.

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`

Это более элегантно?

3 голосов
/ 22 июня 2015

Улучшение ответов @ Marvin и @ Nils Goroll с помощью oneliner, который извлекает pids всех команд в конвейере в переменную массива оболочки:

# 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=..., которая лучше работает со сложными (многострочными) командами.

2 голосов
/ 19 декабря 2013

С вдохновением от ответа @ Demosthenex: с использованием подоболочек:

$ ( echo $BASHPID > pid1; exec vmstat 1 5 ) | tail -1 & 
[1] 17371
$ cat pid1
17370
$ pgrep -fl vmstat
17370 vmstat 1 5
2 голосов
/ 27 июля 2010

Исходя из вашего комментария, я все еще не понимаю, почему вы бы предпочли убить my_prog, чтобы завершить его упорядоченным образом. Десять секунд - это довольно произвольное измерение в многопроцессорной системе, при котором my_prog может генерировать 10 тыс. Строк или 0 строк вывода в зависимости от загрузки системы.

Если вы хотите ограничить вывод my_prog чем-то более определенным, попробуйте

my_prog | head -1000 | awk

без отрыва от оболочки. В худшем случае head закроет свой ввод и my_prog получит SIGPIPE. В лучшем случае измените my_prog, чтобы получить желаемый объем вывода.

добавлено в ответ на комментарий :

Поскольку у вас есть контроль над my_prog, укажите необязательный аргумент -s duration. Тогда где-нибудь в вашем основном цикле вы можете поместить предикат:

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

, где выход, в свою очередь, будет правильно очищать выходные файлы. Если отчаянно и нет места для предиката, это может быть реализовано с помощью alarm (3), который я намеренно не показываю, потому что это плохо.

Суть вашей проблемы в том, что my_prog работает вечно. Все остальное здесь - хак, чтобы обойти это ограничение.

0 голосов
/ 22 марта 2018

Моим решением было запросить 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

Я проверял это в bash и zsh.К сожалению, в bash я не смог перенаправить вывод pipeid в другую команду.Вероятно, потому что этот конвейер запущен в подчиненной оболочке, не в состоянии запросить список заданий ??

0 голосов
/ 18 декабря 2013

Я отчаянно искал хорошее решение, чтобы получить все идентификаторы PID из конвейерной работы, и один многообещающий подход с треском провалился (см. Предыдущие редакции этого ответа).работает с jobs -l с использованием GNU awk:

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; }'
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...