20

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}.

Gilles 'SO- stop being evil'
864k204 gold badges1.8k silver badges2.3k bronze badges
asked Sep 18, 2014 at 0:07
4
  • possible duplicate of How can I get the pid of a process started this way Commented Sep 18, 2014 at 19:34
  • 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. Commented Sep 18, 2014 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. Commented Sep 18, 2014 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. Commented Sep 18, 2014 at 19:48

4 Answers 4

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.

answered Sep 18, 2014 at 0:14
5
  • 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? Commented Sep 18, 2014 at 0: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 Commented Sep 18, 2014 at 0: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. Commented Sep 18, 2014 at 0:26
  • @muru - note it would've been better to make it a new Q referencing this one. Commented Sep 18, 2014 at 2:12
  • @slm duly noted. Will do so in future. Commented Sep 18, 2014 at 2: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.

answered Sep 18, 2014 at 0:45
5
  • Interesting, but this would involve modifying the set of commands. Not much use once the commands have been executed. Commented Sep 18, 2014 at 0: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. Commented Sep 18, 2014 at 4: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? Commented Sep 18, 2014 at 5:41
  • 1
    @muru that would be a sorcery. you need a debugger. Commented Sep 18, 2014 at 5: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: gist.github.com/MatrixManAtYrService/… Commented May 6, 2019 at 2: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
answered Sep 18, 2014 at 1:50
1
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
answered Mar 29, 2020 at 12:46

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.