Подтвердить что ты не робот

Как получить 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 '...', чтобы уточнить, что мне нужно.

4b9b3361

Ответ 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; }'
}