19

I have written a shell script to monitor a directory using the inotifywait utility of inotifyt-tools. I want that script to run continuously in the background, but I also want to be able to stop it when desired.

To make it run continuously, i used while true; like this:

while true;
do #a set of commands that use the inotifywait utility
end

I have saved it in a file in /bin and made it executable. To make it run in background, i used nohup <script-name> & and closed the terminal.

I don't know how do I stop this script. I have looked at the answers here and a very closely related question here.

UPDATE 1: On the basis of the answer of @InfectedRoot below, I have been able to solve my problem using the following strategy. First use

ps -aux | grep script_name

and use sudo kill -9 <pid> to kill the processes. I then had to pgrep inotifywait and use sudo kill -9 <pid> again for the id returned.

This works but i think this is a messy approach, I am looking for a better answer.

UPDATE 2: The answer consists of killing 2 processes. This is important because running the script on the command line initiates 2 processes, 1 the script itself and 2, the inotify process.

4
  • 2
    Removing the -9 option from kill and using just kill will take the mess out of it.
    – Sreeraj
    Dec 13, 2014 at 8:01
  • 1
    Bravo. To get some '$$' into the pun cash box again, I would like to emphasize that the -9 option would be total over-kill here. :P Dec 15, 2014 at 5:35
  • For the sake of completeness: There is a clean approach to kill both processes at once: Instead of killing the processes separately you can kill the process group of the script to kill them at once. The pgid is the same as the pid of the shell script main process and to kill it you prefix the pgid with a minus: kill -- -<pgid>, kill -9 -<pgid>, etc.
    – cg909
    Mar 18, 2017 at 22:30

4 Answers 4

8

To improve, use killall, and also combine the commands:

ps -aux | grep script_name
killall script_name inotifywait

Or do everything in one line:

killall `ps -aux | grep script_name | grep -v grep | awk '{ print $1 }'` && killall inotifywait
5
  • Your above solution works but not the one line command. Please could you check it. I get a error: no process Dec 13, 2014 at 10:27
  • @light94 Error: no process should just mean that you've already killed it. You can test it by opening two other programs, like VLC & Geany, and trying it with them instead. Dec 13, 2014 at 11:54
  • Hey @cremefraiche , as earler said your code works.. but I have been searching for alternate solutions and the most common solution i see is to use kill <pid> . Since, my script does not end in that manner, I am bit worried if my code is improper? Can you please guide me? Dec 14, 2014 at 15:31
  • 1
    You can save on the grep -v grep by turning grep script_name into grep -e "[s]cript_name".
    – nmichaels
    Apr 5, 2018 at 20:34
  • On my machine (Arch Linux), the awk column is 2, not 1, for the PID. This can be used this way: ps -aux | grep <name> | grep -v grep | awk '{ print $2 }' | xargs kill -9
    – Xenhat
    Aug 20, 2020 at 23:36
4

List the background jobs using

# jobs

Then choose the the prior number of job and run

example

# fg 1 

enter image description here

will bring to foreground.

Then kill it using CTRL+C or easier way to find the PID of script using

ps -aux | grep script_name

enter image description here

Then kill using pid

sudo kill -9 pid_number_here
4
  • 3
    The first option is not valid for my case because i close the shell after stating the script and i think jobs command works for the same shell only. Also, i tried the second option and it does kill the process but when i do pgrep inotifywait, i can still see it as a process there. Dec 13, 2014 at 6:39
  • jobs will give the list of jobs even you close the shell. Still the process get finish it will be there. Dec 13, 2014 at 6:52
  • 2
    I tried it but it didn't. Dec 13, 2014 at 6:54
  • @light94 You are right. It happened frequently here that the jobs command really only displayed the running jobs while in the shell. Once I closed a certain terminal window, the only way to access them was via ps (see above for that). So it's for sure that you're not making this up. Dec 15, 2014 at 5:38
3

As you can probably tell there are lots of ways to do this.

Regarding your "UPDATE #2" -- generally speaking, terminating any process in a parent-child hierarchy will generally terminate all the associated processes. But there are many exceptions to this. Ideally, you want to terminate the final 'child' in a process tree, then the parent(s) of this child should exit if they have no other tasks to run. But if you kill a parent, the signal should be relayed down to children when the parent dies and the children should exit as well -- but there are cases where the children processes may ignore the signal (via traps or similar mechanisms) and may continue to run an will be inherited by the 'init' process (or similar.) But this subject of process behavior can get complex and I'll just leave it there...

One method I like if I don't want to use a control script (described next) is to use the 'screen' utility to start and manage the process. The 'screen' command is full of features and can take some time to master. I'd encourage you to read the 'screen' man page for a full explanation. A quick example to start a process in the background would be the command:

screen -d -m /path/to/program

This will start "/path/to/program" inside of a 'screen' session.

You can see your running session with the command:

screen -ls

And at any time you can reconnect to your running program with the command:

screen -r

And then just terminate it with a ^C or whatever.

Besides the beauty of being able to reconnect and disconnect from your process at will is that 'screen' will capture any stdout() that your program might produce.


But my personal preference in these matters is to have a control program that manages the starting and stopping of a process. This can get somewhat complicated and requires some arguably complicated scripting. And like any script, there are dozens of good ways to do it. I've included a bash example of a method I routinely use to start and stop applications. If your task is simple you can insert it directly into the control script -- or you can have this control script call another external program. Note that this example is by no means comprehensive in terms of managing the process. I've left out the possibility of scenarios such as: Making sure the script isn't already running when you use the "start" option, validating that the running PID is actually the process you started (e.g. your script hasn't died and another process been started using the same PID), and validating that the script actually responded (exited) at the first 'kill' request. Doing all these checks can get complicated and I didn't want to make the example too long and complex. You might want to modify the example to practice your shell scripting.

Save the following code to a file called "programctl", make it executable with the command:

chmod 755 programctl

Then edit the file and add your code/script in the case-section that begins with "myscript".

Once everything is in place, assuming the "programctl" is in the current directory, you can start your program with:

./programctl start

And stop it with:

./programctl stop

Cheers.

#!/bin/bash
# Description:  A wrapper script used to stop/start another script.

#--------------------------------------
# Define Global Environment Settings:
#--------------------------------------

# Name and location of a persistent PID file

PIDFILE="/tmp/tmpfile-$LOGNAME.txt"

#--------------------------------------
# Check command line option and run...
# Note that "myscript" should not
# provided by the user.
#--------------------------------------

case $1
in
    myscript)
        # This is where your script would go.
        # If this is a routine 'bash' shell script, you can enter
        # the script below as illustrated in the example.  
        # Or you could simply provide the path and parameters
        # to another script such as /dir/name/command -options

        # Example of an embedded script:

        while true
        do
            # do something over and over...
            sleep 1
        done

        # Example of an external script:

        /usr/local/bin/longrun -x
    ;;

    start)
        # Start your script in the background.
        # (Note that this is a recursive call to the wrapper
        #  itself that effectively runs your script located above.)
        $0 myscript &

        # Save the backgound job process number into a file.
        jobs -p > $PIDFILE

        # Disconnect the job from this shell.
        # (Note that 'disown' command is only in the 'bash' shell.)
        disown %1

        # Print a message indicating the script has been started
        echo "Script has been started..."
    ;;

    stop)
        # Read the process number into the variable called PID
        read PID < $PIDFILE

        # Remove the PIDFILE
        rm -f $PIDFILE

        # Send a 'terminate' signal to process
        kill $PID

        # Print a message indicating the script has been stopped
        echo "Script has been stopped..."
    ;;

    *)
        # Print a "usage" message in case no arguments are supplied
        echo "Usage: $0 start | stop"
    ;;
esac
2
  • I tried the "screen" method you suggested and it works beautifully. I am yet to try the second method. Thank you for the answer. Dec 18, 2014 at 4:41
  • i like this very much!
    – Erce
    Jan 21, 2021 at 18:56
2

You can use ps+grep or pgrep to get the process name/pid; later use killall/pkill to kill process name or use kill to kill pid. All of the followings should work.

killall $(ps aux | grep script_name | grep -v grep | awk '{ print $1 }') && killall inotifywait
(ps -ef | grep script_name | grep -v grep | awk '{ print $1 }' | xargs killall) && killall inotifywait
(ps -ef | grep script_name | grep -v grep | awk '{ print $2 }' | xargs kill) && killall inotifywait
(pgrep -x script_name | xargs kill) && pkill -x inotifywait
pkill -x script_name && pkill -x inotifywait

The most important thing is that you should make sure you only kill the exact process(es) that you are hoping to kill.

pkill/pgrep matches a pattern rather than the exact name, so is more dangerous; here -x is added to match the exact name.

Also, when using pgrep/pkill you may need

  • -f to match the full command line(like ps aux does)
  • -a to also print the process name.
4
  • After executing the above, i get a usage error in each. i am copy pasting and replacing the scriptname. I get usage for kill as output Dec 13, 2014 at 10:25
  • Is script_name running? It works for me(debian jessie) Dec 13, 2014 at 10:28
  • yes it is running. and your solution is almost same as @cremefraiche solutions above so I had expected it to work the same way :/ Dec 13, 2014 at 10:40
  • Yes, I was somewhat summarizing the possible ways to do so, especially adding pgrep/pkill. If that still gets wrong you might try pgrep without -x. Basically I don't think it's a good to kill the process within one line command without checking whether other processes are matched. Dec 13, 2014 at 12:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .