8

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.

  • 2
    Removing the -9 option from kill and using just kill will take the mess out of it. – Sree Dec 13 '14 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 – syntaxerror Dec 15 '14 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 '17 at 22:30
5

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
  • Your above solution works but not the one line command. Please could you check it. I get a error: no process – light94 Dec 13 '14 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. – cremefraiche Dec 13 '14 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? – light94 Dec 14 '14 at 15:31
  • You can save on the grep -v grep by turning grep script_name into grep -e "[s]cript_name". – nmichaels Apr 5 '18 at 20:34
3

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
  • 2
    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. – light94 Dec 13 '14 at 6:39
  • jobs will give the list of jobs even you close the shell. Still the process get finish it will be there. – Babin Lonston Dec 13 '14 at 6:52
  • 2
    I tried it but it didn't. – light94 Dec 13 '14 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. – syntaxerror Dec 15 '14 at 5:38
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.
  • 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 – light94 Dec 13 '14 at 10:25
  • Is script_name running? It works for me(debian jessie) – Hongxu Chen Dec 13 '14 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 :/ – light94 Dec 13 '14 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. – Hongxu Chen Dec 13 '14 at 12:24
1

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
  • I tried the "screen" method you suggested and it works beautifully. I am yet to try the second method. Thank you for the answer. – light94 Dec 18 '14 at 4:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.