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}
.
4 Answers 4
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– muru2014年09月18日 00:21:26 +00:00Commented 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 meuser41515– user415152014年09月18日 00:23:11 +00:00Commented 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.muru– muru2014年09月18日 00:26:12 +00:00Commented Sep 18, 2014 at 0:26
-
@muru - note it would've been better to make it a new Q referencing this one.2014年09月18日 02:12:27 +00:00Commented Sep 18, 2014 at 2:12
-
@slm duly noted. Will do so in future.muru– muru2014年09月18日 02:15:15 +00:00Commented Sep 18, 2014 at 2:15
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.
-
Interesting, but this would involve modifying the set of commands. Not much use once the commands have been executed.muru– muru2014年09月18日 00:49:21 +00:00Commented 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 withSIGPIPE
. Do you wantcmdi
- this.mikeserv– mikeserv2014年09月18日 04:35:56 +00:00Commented 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?muru– muru2014年09月18日 05:41:59 +00:00Commented Sep 18, 2014 at 5:41
-
1@muru that would be a sorcery. you need a debugger.mikeserv– mikeserv2014年09月18日 05:43:04 +00:00Commented 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/…MatrixManAtYrService– MatrixManAtYrService2019年05月06日 02:41:40 +00:00Commented May 6, 2019 at 2:41
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
-
For the curious, there's more about this kind of thing here: unix.stackexchange.com/a/486233/146169MatrixManAtYrService– MatrixManAtYrService2019年05月06日 00:42:13 +00:00Commented May 6, 2019 at 0:42
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
cat /var/run/out | nc -l 8080
is only superficially similar tocmd1 | 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.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.