How to get the PID of a process that is piped to another process in Bash?

41,541

Solution 1

Write tail's PID to file descriptor 3, and then capture it from there.

( tail -f $1 & echo $! >&3 ) 3>pid | nc -l -p 9977
kill $(<pid)

Solution 2

Another option: use a redirect to subshell. This changes the order in which background processes are started, so $! gives PID of the tail process.

tail -f $1 > >(nc -l -p 9977) &
wait $!

Solution 3

how about this:

jobs -x echo %1

%1 is for first job in chain, %2 for second, etc. jobs -x replaces job specifier with PID.

Solution 4

This works for me (SLES Linux):

tail -F xxxx | tee -a yyyy &
export TAIL_PID=`jobs -p`
# export TEE_PID="$!"

The ps|grep|kill trick mentioned in this thread would not work if a user can run the script for two "instances" on the same machine.

jobs -x echo %1 did not work for me (man page not having the -x flag) but gave me the idea to try jobs -p.

Solution 5

Maybe you could use a fifo, so that you can capture the pid of the first process, e.g.:

FIFO=my_fifo

rm -f $FIFO
mkfifo $FIFO

tail -f $1 > $FIFO &
TAIL_PID=$!

cat $FIFO | nc -l -p 9977

kill $TAIL_PID

rm -f $FIFO
Share:
41,541
Ertuğ Karamatlı
Author by

Ertuğ Karamatlı

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

Updated on May 14, 2020

Comments

  • Ertuğ Karamatlı
    Ertuğ Karamatlı over 3 years

    I am trying to implement a simple log server in Bash. It should take a file as a parameter and serve it on a port with netcat.

    ( tail -f $1 & ) | nc -l -p 9977
    

    But the problem is that when the netcat terminates, tail is left behind running. (Clarification: If I don't fork the tail process it will continue to run forever even the netcat terminates.)

    If I somehow know the PID of the tail then I could kill it afterwards.
    Obviously, using $! will return the PID of netcat.

    How can I get the PID of the tail process?

  • Ertuğ Karamatlı
    Ertuğ Karamatlı about 14 years
    Yes I have tried It before. The problem about using fifo is the same: pipe never gets terminated so cat stays running even netcat terminates. Also the control stays in the cat line so it never executes kill.
  • martin clayton
    martin clayton about 14 years
    That's odd - the script above worked perfectly for me on Mac OS X. Only slight difference was that I omitted the '-p' flag for nc.
  • Ertuğ Karamatlı
    Ertuğ Karamatlı about 14 years
    Maybe its a platform issue (about how to handle pipes). I'm trying it on a linux machine. thanks for your answer anyway!
  • Ertuğ Karamatlı
    Ertuğ Karamatlı about 14 years
    Oh, I have never thought of it. I wish it worked :) But it's still not terminating because of the nature of "tail -f"
  • Ertuğ Karamatlı
    Ertuğ Karamatlı about 14 years
    without the -f, its OK. But I want to serve it in real time.
  • Ertuğ Karamatlı
    Ertuğ Karamatlı about 14 years
    I couldn't get it using the ppid because it is detached when I fork it in a subshell. But I managed to grep it using the args param of the ps program.
  • tripleee
    tripleee about 12 years
    Shouldn't the PID of the tail command be available in $! so that you could simply do kill $! instead of kill_tail?
  • Wernight
    Wernight almost 12 years
    I used a variant: ( tail -f $1 & echo $! >pid ) | nc -l -p 9977 (not sure why using file descriptor 3 would help when finally redirecting to a file)
  • Wernight
    Wernight almost 12 years
    Not sure why but my solution fails after a couple of log lines are output. Probably when the pipe buffer is full. Then the initial process seems to be waiting for the pipe to be processed.
  • hfs
    hfs over 11 years
    What if you remove the cat and use nc -l -p 9977 < $FIFO?
  • Bryan Larsen
    Bryan Larsen almost 10 years
    The advantage of this approach is that after the wait, $? also holds the exit status
  • William Luc Ritchie
    William Luc Ritchie about 9 years
    This seems like the cleanest approach by a long shot. Does it change anything (compared to standard piping) from the perspective of either process other than the start order?
  • William Luc Ritchie
    William Luc Ritchie about 9 years
    Actually, the syntax on that redirection is wrong (though the principle is correct). >(foo) is substituted for the name of the new file descriptor, whereas > >(foo) actually redirects output to it. You want the first line to be tail -f $1 > >(nc -l -p 9977) &.
  • JATothrim
    JATothrim almost 9 years
    This must be the cleanest + shortest solution to 'how to get pid of any process earlier in chain' Thank You! It also works with '&' getting the pid of process earlier in the chain that is running in background! E.g. dd if=/dev/urandom bs=1M count=1024 | sha1sum & pid=$(jobs - x echo %1) kill -USR1 $pid
  • pipe_of_sharks
    pipe_of_sharks almost 5 years
    Closing stdin of tail with eg. </dev/null or exec as in the example is nice to do, so that no streams are left dangling around unconnected, but not strictly necessary for the tail command in particular, since tail never reads from stdin when given a filename. Could be left out when the first coproc command is tail.
  • sl0815
    sl0815 over 4 years
    exactly what I needed! Thanks! Just redirecting the pid didn't work in my case.
  • TimeS
    TimeS over 4 years
    If you want to capture both stdout and stderr in the subshell use &> for the redirection. Example: tail -f $1 &> >(nc -l -p 9977) &.
  • ivan_pozdeev
    ivan_pozdeev over 3 years
    The answer is wrong. Only one job ID is assigned, for the entire pipeline: shopt -s lastpipe; sleep 1 | cat | jobs -> [1] Running sleep 1 | cat. It has the PID of the 1st command on the pipeline but jobs -l prints all the PIDs: shopt -s lastpipe; sleep 1 | cat | jobs -l; jobs -p -> [1] 5394 Running sleep 1 5395 | cat 5394.
  • ivan_pozdeev
    ivan_pozdeev over 3 years
    Note that you can only get the PID of the first command on the pipeline this way. jobs -l would print PIDs of all commands but you'll have to parse its output to extract them.
  • ivan_pozdeev
    ivan_pozdeev over 3 years
    Note that coproc is new in Bash 4 (e.g. OSX still uses 3.2). It is available in OSX's zsh though.
  • Fabian Streitel
    Fabian Streitel about 3 years
    Unlike many of the other comments, this also works in /bin/sh to obtain the PID of the first command (process group leader), e.g. to be able to kill the entire pipeline later on.
  • Apiwat Chantawibul
    Apiwat Chantawibul almost 2 years
    Correction: pipe | also doesn't wait for tail to finish. The differences are a bit more interesting.