Search code examples
linuxbashunixinotifywait

Why does this inotifywait shellscript shop up with two PIDs?


I am learning to use inotifywait, specifically by using the script at: https://unix.stackexchange.com/questions/24952/script-to-monitor-folder-for-new-files. What I do not understand is why my scripts always appear two times when I use pid x.

36285 pts/1    S+     0:00 /bin/bash ./observe2.sh /home/user1/testfolder
36286 pts/1    S+     0:00 inotifywait -m /home/user1/testfolder -e create -e moved_to
36287 pts/1    S+     0:00 /bin/bash ./observe2.sh /home/user1/testfolder

For quicker testing, I changed the linked script so that you can pass any folder via $1 to be observed, and saved as observe2.sh:

#!/bin/bash
inotifywait -m $1 -e create -e moved_to |
    while read path action file; do
        echo "The file '$file' appeared in directory '$path' via '$action'"
        # do something with the file
    done

Why does the script process show up two times? Is there a fork somewhere in the process? Could somebody explain why exactly this behavior of two processes is happening?


Solution

  • Because it has a pipeline.

    A pipeline forks a subshell -- a second process -- and connects them. In the case of foo | bar -- where those are both external, non-shell commands -- the subshells exec the actual commands, and thus lose their process tree entries. When an element of that pipeline is written in shell, a shell to execute it remains in the process tree.


    That said, I'd suggest[1] writing it a bit differently:

    while read -r path action file; do
        echo "The file '$file' appeared in directory '$path' via '$action'"
        # do something with the file
    done < <(exec inotifywait -m "$1" -e create -e moved_to)
    

    This will keep the while loop in the main process, and the exec makes the forked-off subshell replace itself with inotifywait. Thus, changes you make inside the while loop -- such as setting variables in your script -- will persist even after the script moves on from the loop, whereas if you put the loop in a pipeline, any variables set in it are scoped to the subshell. See BashFAQ #24 for details.


    [1] - Actually, I'd suggest writing it even more differently than that, if you want to cover all the corner cases thoroughly. Since POSIX filenames can contain any character other than NUL or / (and / can, of course, exist in pathnames), space- or newline-separating names is a very bad idea; there are also bugs in the existing implementation around names that end in whitespace. Unfortunately, since inotifywait doesn't allow custom format strings to contain \0 to NUL-delimit them, getting completely unambiguous output from it is quite tricky; it's been covered in other answers on StackOverflow, and I'm not much inclined to reproduce their contents here.