Как получить PID процесса в конвейере
Рассмотрим следующий упрощенный пример:
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 '...'
, чтобы уточнить, что мне нужно.
Ответы
Ответ 1
Мне удалось решить проблему, явно называя канал, используя mkfifo
.
Шаг 1: mkfifo capture
.
Шаг 2. Запустите этот script
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
Просто такая же проблема. Мое решение:
process_1 | process_2 &
PID_OF_PROCESS_2=$!
PID_OF_PROCESS_1=`jobs -p`
Просто убедитесь, что process_1 - это первый фоновый процесс. В противном случае вам нужно проанализировать полный вывод jobs -l
.
Ответ 3
Вот решение без оберток или временных файлов. Это работает только для фонового конвейера, выход которого удаляется от stdout содержащего script, как в вашем случае. Предположим, вы хотите сделать:
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 )
При необходимости вы также можете закрыть fd 3 (для cmd*
) и fd 4 (для cmd2
) с помощью 3>&-
и 4<&-
соответственно. Если вы это сделаете, для cmd2
убедитесь, что вы закрыли fd 4 только после перенаправления fd 0 из него.
Ответ 4
Добавить оболочку оболочки вокруг вашей команды и захватить 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`
Это более элегантно?
Ответ 5
Улучшение @Marvin и @Nils Goroll отвечает с помощью oneliner, который извлекает pids для всех команд в pipe в переменную массива оболочки:
# 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
UPDATE 2017-11-13: Улучшена команда pids=...
, которая лучше работает с сложными (многострочными) командами.
Ответ 6
Основываясь на вашем комментарии, я все еще не понимаю, почему вы предпочли бы убить my_prog
, чтобы оно было закончено упорядоченным образом. Десять секунд - довольно произвольное измерение в многопроцессорной системе, в которой my_prog
может генерировать 10k строк или 0 строк вывода в зависимости от загрузки системы.
Если вы хотите ограничить вывод my_prog
чем-то более определенным, попробуйте
my_prog | head -1000 | awk
без отсоединения от оболочки. В худшем случае голова закроет свой вход, а my_prog получит SIGPIPE. В лучшем случае измените my_prog
, чтобы он выдавал желаемый объем вывода.
добавлено в ответ на комментарий:
Если у вас есть контроль над my_prog
, дайте ему необязательный аргумент -s duration
. Затем где-то в основной петле вы можете поместить предикат:
if (duration_exceeded()) {
exit(0);
}
где exit, в свою очередь, правильно очистит выходные файлы. Если отчаянно и нет места, чтобы положить предикат, это может быть реализовано с помощью будильника (3), который я намеренно не показываю, потому что это плохо.
Ядро вашей проблемы состоит в том, что my_prog
работает вечно. Все остальное здесь - хак, чтобы обойти это ограничение.
Ответ 7
С вдохновением от ответа @Demosthenex: использование подоболочек:
$ ( echo $BASHPID > pid1; exec vmstat 1 5 ) | tail -1 &
[1] 17371
$ cat pid1
17370
$ pgrep -fl vmstat
17370 vmstat 1 5
Ответ 8
Я отчаянно искал хорошее решение, чтобы получить все 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; }'
}