19

If, in bash, I execute:

cmd1 | cmd2 | ... | cmdi | ... | cmdn &

where the cmd{1..n} may not be distinct, how do I get the PID of cmdi? Alternatively, how can I signal the cmdi process? (For example, send it SIGUSR1?) pkill/pgrep, pidof etc. don't look like good answers, since other instances of cmdi maybe running, including as part of the same pipeline. jobs -p gives the PID of cmd1, for me.

i can be anything in {1..n}.

muru
  • 72,889
  • 1
    @G-Man Care to explain? I see only superficial similarity, and as I explained in Ramesh's answer, modifying the set of commands is of not much use. – muru Sep 18 '14 at 19:37
  • Superficial similarity? cat /var/run/out | nc -l 8080 is only superficially similar to cmd1 | cmd2? Your constraint, that you want to type the bare-bones pipeline *and then* recover the PIDs, is (1) not stated in the question, and (2) unlikely to allow for a good, general solution. – G-Man Says 'Reinstate Monica' Sep 18 '14 at 19:43
  • @G-Man On the contrary, you are imposing constraints that simple aren't stated. cmd1 | cmd2 is a very special case where both PIDs are easily obtainable. Did I say anything about n? So why would you assume n=2? Did I say anything about what cmdi is? So why would you assume I could modify cmdi? I am asking for a general solution and you are imposing restrictions. – muru Sep 18 '14 at 19:48

4 Answers4

7

For the original version of the question, when only the last command's PID was desired, the special variable $! is perfect.

foo | bar | baz &
baz_pid=$!

There's no similar easy access to the PIDs of the other processes.

It took a long time for $pipestatus (zsh) and $PIPESTATUS (bash) to be added, finally giving us access to all of the exit statuses in a pipeline, in addition to the $? for the last one that has been around since the original Bourne shell. Maybe something analogous will happen with $! eventually.

  • Would you mind if I edited the question to ask for the PID of an arbitrary command in the list as well? Or should I start a new question? – muru Sep 18 '14 at 00:21
  • You'll probably have to wait a lot longer for an answer to that one. I don't have strong feelings about stackexchange site organization so separate question, edit question, whatever... won't bother me –  Sep 18 '14 at 00:23
  • That's okay, the immediate problem is solved, now curiosity is in charge. I'll edit it then. Just a heads-up, since I have seen questions change drastically and leaving the older answers looking very out-of-place. – muru Sep 18 '14 at 00:26
  • @muru - note it would've been better to make it a new Q referencing this one. – slm Sep 18 '14 at 02:12
  • @slm duly noted. Will do so in future. – muru Sep 18 '14 at 02:15
4

I think you could do something as suggested here.

(ls -l | echo "Hello" | df -h & echo $! >&3 ) 3>pid

Here in the above example, I have retrieved the pid of third piped process and noted it down to the file pid. I could note it down for any piped process.

Ramesh
  • 39,297
  • Interesting, but this would involve modifying the set of commands. Not much use once the commands have been executed. – muru Sep 18 '14 at 00:49
  • @muru - what? what use is any PID once it has finished execution? do you want the pipeline's PID? jobs -p. signal it with SIGPIPE. Do you want cmdi - this. – mikeserv Sep 18 '14 at 04:35
  • 1
    @mikeserv Not if they are in the background, running as we speak. By what sorcery am I supposed to modify the command line for that? – muru Sep 18 '14 at 05:41
  • 1
    @muru that would be a sorcery. you need a debugger. – mikeserv Sep 18 '14 at 05:43
  • I find this to be a useful pattern for starting background processes, waiting for them to reach some state, and then killing them. In case anybody is interested: https://gist.github.com/MatrixManAtYrService/d25aafb4131a8a14ca2a6d05a3dd88e9 – MatrixManAtYrService May 06 '19 at 02:41
2

A not-very-portable, Linux-specific solution could be to track the processes using the pipes that connect them. We can get the PIDs of the first (jobs -p) and last ($!) commands in the pipeline. Using either PID, this script could do the job:

#! /bin/bash

PROC=$1
echo $PROC

if [[ $(readlink /proc/$PROC/fd/1) =~ ^pipe: ]]
then
    # Assuming first process in chain...
    NEXT_FD=1
elif [[ $(readlink /proc/$PROC/fd/0) =~ ^pipe: ]]
then
    # Last process in chain...
    NEXT_FD=0
else
    # Doesn't look like a pipe.
    exit
fi

NEXT_PROC_PIPE=$(readlink /proc/$PROC/fd/$NEXT_FD)

while [[ $NEXT_PROC_PIPE =~ ^pipe: ]] 
do
    PROC=$(find /proc/*/fd -type l -printf "%p/%l\n" 2>/dev/null | awk -F'/' '($6 == "'"$NEXT_PROC_PIPE"'") && ($3 != "'$PROC'" ) {print $3}')
    NEXT_PROC_PIPE=$(readlink /proc/$PROC/fd/$NEXT_FD)
    echo $PROC
done
muru
  • 72,889
0

I use zero-based arrays here in this code. Just be careful what you run by eval.

#!/bin/bash

cmd=('sleep 10' 'sleep 2' 'sleep 5')
first=1
for c in "${cmd[@]}"; do
  ((first)) && { pipe=$c; first=0; } || pipe+='|'$c
done
shopt -u lastpipe
eval $pipe &

printf 'Pipe:\n%s\n\n' "$pipe"

shellpid=$BASHPID
parent=$(ps -o pid= --ppid $shellpid | head -n -1)
declare -a pids=()
mapfile -t pids < <(printf '%s\n' $(ps -o pid= --ppid $parent))
printf '%s\n' 'Listing the arrays:'
printf '%2s %6s %s\n' i PID command
for i in "${!cmd[@]}"; do
    printf '%2d %6d %s\n' "$i" "${pids[i]}" "${cmd[i]}"
done

printf '\n%s\n' 'ps listing:'
ps xao pid,ppid,command | head -n 1
ps xao pid,ppid,command | tail | head -n -3
jarno
  • 620