7

(or How to kill the child process)?

inotifywait -mqr --format '%w %f %e' $feedDir | while read dir file event
do
#something
done &

echo $! #5431

ps eg:

 >$ ps
  PID TTY          TIME CMD
 2867 pts/3    00:00:02 bash
 5430 pts/3    00:00:00 inotifywait
 5431 pts/3    00:00:00 bash
 5454 pts/3    00:00:00 ps

It seems if I kill 5431 then 5430 (inotifywait) will be left running, but if I kill 5430 then both processes die. I don't suppose I can reliably assume that the pid of inotifywait will always be 1 less than $!?

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
andrew
  • 9,313
  • 7
  • 30
  • 61
  • Are you sure $$ is 5431? I'd think the 5431 is the `while` you piping inotify into. – Jan Matějka May 12 '14 at 18:45
  • I'd certainly expect the $$ to be lower than the pid of inotifywait unless the pid numbers wrapped and then the chance they will be right next to each other (±1) is pretty slim – Jan Matějka May 12 '14 at 18:47
  • pretty sure, I manually typed `echo $$` followed by `ps` the output was exactly as posted – andrew May 12 '14 at 18:47
  • 1
    Hrm, "manualy"? Run it in a script as is in the code sample. – Jan Matějka May 12 '14 at 18:49
  • And what does `echo $!` shows ? – PradyJord May 12 '14 at 18:50
  • Also what do you actually want? Be able to kill inotify at some later point in time? You can just `pkill inotify` unless there is more of them running. And I suspect you could just save the value of `$!` obtained right after starting the background process and killing that should take down the whole pipeline – Jan Matějka May 12 '14 at 18:50
  • Ok sorry about that, I actually did mean `$!` that was a typo in the post Edited – andrew May 12 '14 at 18:51
  • 1
    http://stackoverflow.com/questions/1652680/how-to-get-the-pid-of-a-process-that-is-piped-to-another-process-in-bash does this help? – Jan Matějka May 12 '14 at 18:57
  • Here's also a good idea with writing $$ somewhere and then execing the inotify, that whole in subshell that pipes into the while http://stackoverflow.com/questions/3345460/how-to-get-the-pid-of-a-process-in-a-pipeline – Jan Matějka May 12 '14 at 19:02
  • Thanks @yaccz should be able to find a solution in there somewhere – andrew May 12 '14 at 19:21
  • 2
    If you must really find the PID, you could do so by separating the two commands via a named pipe. You would create a named pipe using `mknod` or `mkfifo` and the approach is: `command1 > PIPE &; pid1=$PID; command2 < PIPE`. See http://www.linuxjournal.com/content/using-named-pipes-fifos-bash for more information on named pipes. – haridsv May 20 '14 at 00:54
  • Assuming that the other PID in the pipeline is one less than the last will often be correct, but is far from guaranteed to be correct. It depends on what other activity there is in the system, and on whether your system generates consecutive PIDs in the first place (yours seems to, but not all systems do — AIX is a counter-example where it can be configured so that PIDs are generated at random). – Jonathan Leffler May 27 '14 at 01:43

2 Answers2

3

When we run a pipe, each command is executed in a separated process. The interpreter waits for the last one but if we use ampersand (&).

cmd1 | cmd2 &

The pid of processes will be probably close, but we cannot assume it reliably. In the case where the last command is a bash reserved word as while, it creates a dedicated bash (that's why your 'dir', 'file' variables won't exist after the done keyword). Example:

ps # shows one bash process
echo "azerty" | while read line; do ps; done # shows one more bash

When the first command exits, the second one will terminate because the read on the pipe return EOF. When the second command exits, the first command will be terminated by the signal SIGPIPE (write on a pipe with no reader) when it tries to write to the pipe. But if the command waits indefinitely... it is not terminated.

echo "$!" prints the pid of the last command executed in background. In your case, the bash process that is executing the while loop.

You can find the pid of "inotifywait" with the following syntax. But it's uggly:

(inotifywait ... & echo "$!">inotifywait.pid) | \
while read dir file event
do
    #something
done &
cat inotifywait.pid # prints pid of inotifywait

If you don't want the pid, but just be sure the process will be terminated, you can use the -t option of inotifywait:

(while true; do inotifywait -t 10 ...; done)| \
while read dir file event
do
    #something
done &
kill "$!" # kill the while loop

None of this solution are nice. What is your real achievement? Maybe we can find a more elegant solution.

Community
  • 1
  • 1
mcoolive
  • 3,805
  • 1
  • 27
  • 32
1

If your goal is to make sure all of the children can be killed or interrupted elegantly. If you're using BusyBox's Ash, you don't have process substitution. If you don't want to use an fd either, check out this solution.

#!/bin/sh

pid=$$

terminate() {
  pkill -9 -P "$pid"
}

trap terminate SIGHUP SIGINT SIGQUIT SIGTERM

# do your stuff here, note: should be run in the background {{{
inotifywait -mqr --format '%w %f %e' $feedDir | while read dir file event
do
#something
done &
# }}}

# Either pkill -9 -P "$pid" here

wait

# or pkill -9 -P "$pid" here

Or in another shell:

kill <pid ($$)>
Johann Chang
  • 1,281
  • 2
  • 14
  • 25