Bash script that kills other instances of itself if they're running - bash

So, I want to make a bash script, and I'm going to have it run on boot, but I'd like to update the script if I need to and run it without a reboot, so what I want to do is make the script check if there is any other instances of it running when it is loaded, and terninate any instances of the script other than itself. I want it to check instances of bash and get the path of the scripts that are being ran and kill any instances of scripts that have the same path name as it's own. How can I do this?
Example: If I am in directory /foo/bar and I run the script ../tball/script.sh, it will kill any instances of bash that are running the script /foo/tball/script.sh if they exist.

Here's the basis
kill_others() {
local mypid=$$ # capture this run's pid
declare pids=($(pgrep -f ${0##*/} # get all the pids running this script
for pid in ${pids[#]/$mypid/}; do # cycle through all pids except this one
kill $pid # kill the other pids
sleep 1 # give time to complete
done
}
declare -i count=0
while [[ $(pgrep -f ${0##*/}|wc -l) -gt 1 ]]; do
kill_outhers
((++count))
if [[ $count -gt 10 ]]; then
echo "ERROR: can't kill pids" >&2
exit 1
fi
done

The best approach is a file containing the PID of the process in a volatile filesystem like this:
echo $$ > /run/script.pid
You could refine it further by checking if that PID exists with:
if [ ! -d /proc/$(< /run/script.pid) ] ; then
rm /run/script.pid
fi
In your script you should have something like this, to remove the file on exit or if it receives a signal that kills the process:
trap "rm -f /run/script.pid" EXIT INT QUIT TERM
EDIT: Or you could append the PID to a well known pathname and kill all instances of the script with something like this before saving the PID:
kill $(< /run/script.pid) ; sleep 10 ; kill -9 $(< /run/script.pid)

Related

Is there a command, or programmatic way I can use to extract PIDs, and put them in a space-separated list? [duplicate]

I'm writing a bash script, which does several things.
In the beginning it starts several monitor scripts, each of them runs some other tools.
At the end of my main script, I would like to kill all things that were spawned from my shell.
So, it might looks like this:
#!/bin/bash
some_monitor1.sh &
some_monitor2.sh &
some_monitor3.sh &
do_some_work
...
kill_subprocesses
The thing is that most of these monitors spawn their own subprocesses, so doing (for example): killall some_monitor1.sh will not always help.
Any other way to handle this situation?
pkill -P $$
will fit (just kills it's own descendants)
EDIT: I got a downvote, don't know why. Anyway here is the help of -P
-P, --parent ppid,...
Only match processes whose parent process ID is listed.
and $$ is the process id of the script itself
After starting each child process, you can get its id with
ID=$!
Then you can use the stored PIDs to find and kill all grandchild etc. processes as described here or here.
If you use a negative PID with kill it will kill a process group. Example:
kill -- -1234
Extending pihentagy's answer to recursively kill all descendants (not just children):
kill_descendant_processes() {
local pid="$1"
local and_self="${2:-false}"
if children="$(pgrep -P "$pid")"; then
for child in $children; do
kill_descendant_processes "$child" true
done
fi
if [[ "$and_self" == true ]]; then
kill -9 "$pid"
fi
}
Now
kill_descendant_processes $$
will kill descedants of the current script/shell.
(Tested on Mac OS 10.9.5. Only depends on pgrep and kill)
kill $(jobs -p)
Rhys Ulerich's suggestion:
Caveat a race condition, using [code below] accomplishes what Jürgen suggested without causing an error when no jobs exist
[[ -z "$(jobs -p)" ]] || kill $(jobs -p)
pkill with optioin "-P" should help:
pkill -P $(pgrep some_monitor1.sh)
from man page:
-P ppid,...
Only match processes whose parent process ID is listed.
There are some discussions on linuxquests.org, please check:
http://www.linuxquestions.org/questions/programming-9/use-only-one-kill-to-kill-father-and-child-processes-665753/
I like the following straightforward approach: start the subprocesses with an environment variable with some name/value and use this to kill the subprocesses later. Most convenient is to use the process-id of the running bash script i.e. $$. This also works when subprocesses starts another subprocesses as the environment is inherited.
So start the subprocesses like this:
MY_SCRIPT_TOKEN=$$ some_monitor1.sh &
MY_SCRIPT_TOKEN=$$ some_monitor2.sh &
And afterwards kill them like this:
ps -Eef | grep "MY_SCRIPT_TOKEN=$$" | awk '{print $2}' | xargs kill
Similar to above, just a minor tweak to kill all processes indicated by ps:
ps -o pid= | tail -n +2 | xargs kill -9
Perhaps sloppy / fragile, but seemed to work at first blush. Relies on fact that current process ($$) tends to be first line.
Description of commands, in order:
Print PIDs for processes in current terminal, excl. header column
Start from Line 2 (excl. current terminal's shell)
Kill those procs
I've incorporated a bunch of the suggestions from the answers here into a single function. It gives time for processes to exit, murders them if they take too long, and doesn't have to grep through output (eg, via ps)
#!/bin/bash
# This function will kill all sub jobs.
function KillJobs() {
[[ -z "$(jobs -p)" ]] && return # no jobs to kill
local SIG="INT" # default to a gentle goodbye
[[ ! -z "$1" ]] && SIG="$1" # optionally send a different signal
# my version of 'kill' doesn't seem to understand `kill -- -${PID}`
#jobs -p | xargs -I%% kill -s "$SIG" -- -%% # kill each job's processes group
jobs -p | xargs kill -s "$SIG" # kill each job's processes group
## give the processes a moment to die, before forcing them to.
[[ "$SIG" != "KILL" ]] && {
sleep 0.2
KillJobs "KILL"
}
}
I also tried to get a variation working with pkill, but on my system (xubuntu 21.10) it does absolutely nothing.
#!/bin/bash
# This function doesn't seem to work.
function KillChildren() {
local SIG="INT" # default to a gentle goodbye
[[ ! -z "$1" ]] && SIG="$1" # optionally send a different signal
pkill --signal "$SIG" -P $$ # kill descendent's and their processes groups
[[ "$SIG" != "KILL" ]] && {
# give them a moment to die before we force them to.
sleep 0.2
KillChildren "KILL" ;
}
}

Check if bash script already running except itself with arguments

So I've looked up other questions and answers for this and as you can imagine, there are lots of ways to find this. However, my situation is kind of different.
I'm able to check whether a bash script is already running or not and I want to kill the script if it's already running.
The problem is that with the below code, -since I'm running this within the same script- the script kills itself too because it sees a script already running.
result=`ps aux | grep -i "myscript.sh" | grep -v "grep" | wc -l`
if [ $result -ge 1 ]
then
echo "script is running"
else
echo "script is not running"
fi
So how can I check if a script is already running besides it's own self and kill itself if there's another instance of the same script is running, else, continue without killing itself.
I thought I could combine the above code with $$ command to find the script's own PID and differentiate them this way but I'm not sure how to do that.
Also a side note, my script can be run multiple times at the same time within the same machine but with different arguments and that's fine. I only need to identify if script is already running with the same arguments.
pid=$(pgrep myscript.sh | grep -x -v $$)
# filter non-existent pids
pid=$(<<<"$pid" xargs -n1 sh -c 'kill -0 "$1" 2>/dev/null && echo "$1"' --)
if [ -n "$pid" ]; then
echo "Other script is running with pid $pid"
echo "Killing him!"
kill $pid
fi
pgrep lists the pids that match the name myscript.sh. From the list we filter current $$ shell with grep -v. It the result is non-empty, then you could kill the other pid.
Without the xargs, it would work, but the pgrep myscript.sh will pick up the temporary pid created for command substitution or the pipe. So the pid will never be empty and the kill will always execute complaining about the non-existent process. To do that, for each pid in pids, I check if the pid exists with kill -0. If it does, then it is outputted, effectively filtering all nonexistent pids.
You could also use a normal for loop to filter the pids:
# filter non-existent pids
pid=$(
for i in $pid; do
if kill -0 "$i" 2>/dev/null; then
echo "$i"
fi
done
)
Alternatively, you could use flock to lock the file and use lsof to list current open files with filtering the current one. As it is now, I think it will kill also editors that are editing the file and such. I believe the lsof output could be better filtered to accommodate this.
if [ "${FLOCKER}" != "$0" ]; then
pids=$(lsof -p "^$$" -- ./myscript.sh | awk 'NR>1{print $2}')
if [ -n "$pids" ]; then
echo "Other processes with $(echo $pids) found. Killing them"
kill $pids
fi
exec env FLOCKER="$0" flock -en "$0" "$0" "$#"
fi
I would go with either of 2 ways to solve this problem.
1st solution: Create a watchdog file lets say a .lck file kind of on a location before starting the script's execution(Make sure we use trap etc commands in case script is aborted so that .lck file should be removed) AND remove it once execution of script is completed successfully.
Example script for 1st solution: This is just an example a test one. We need to take care of interruptions in the script, lets say script got interrupted by a command or etc then we could use trap in it too, since at that time it would have not been completed but you may need to kick it off again(since last time it was not completed).
cat file.ksh
#!/bin/bash
PWD=`pwd`
watchdog_file="$PWD/script.lck"
if [[ -f "$watchdog_file" ]]
then
echo "Please wait script is still running, exiting from script now.."
exit 1;
else
touch $watchdog_file
fi
while true
do
echo "singh" > test1
done
if [[ -f "$watchdog_file" ]]
then
rm "$watchdog_file"
fi
2nd solution: Take pid of current running shell using $$ save it in a file. Then check if that process is still running come out of script if NOT running then move on to run statements in script.

locking a PID file

I have a function in a bash script that runs indefinitely in background and that shall be terminated by running again the same script. It is a sort of switch, when I invoke this script it starts or kills the function if already running. To do this I use a PID file:
#!/bin/bash
background_function() {
...
}
if [[ ! -s myscript.pid ]]
then
background_function &
echo $! > myscript.pid
else
kill $(cat myscript.pid) && rm myscript.pid
fi
Now, I would like to avoid multiple instances running and race conditions. I tried to use flock and I rewrote the above code in this way:
#!/bin/bash
background_function() {
...
}
exec 200>myscript.pid
if flock -n 200
then
background_function &
echo $! > myscript.pid
else
kill $(cat myscript.pid) && rm myscript.pid
fi
In doing so, however, I have a lock on the pid file but every time I launch the script again the pid file is rewritten by exec 200>myscript.pid and therefore I am unable to retrieve the PID of the already running instance and kill it.
What can I do? Should I use two different files, a pid file and a lock file? Or would it be better to implement other lock mechanisms by using mkdir and touch? Thanks.
If an echo $$ is atomic enough for you, you could use:
echo $$ >> lock.pid
lockedby=`head -1 lock.pid`
if [ $$ != $lockedby ] ; then
kill -9 $lockedby
echo $$ > lock.pid
echo "Murdered $lockedby because it had the lock"
fi
# do things in the script
rm lock.pid

How to kill all subprocesses of shell?

I'm writing a bash script, which does several things.
In the beginning it starts several monitor scripts, each of them runs some other tools.
At the end of my main script, I would like to kill all things that were spawned from my shell.
So, it might looks like this:
#!/bin/bash
some_monitor1.sh &
some_monitor2.sh &
some_monitor3.sh &
do_some_work
...
kill_subprocesses
The thing is that most of these monitors spawn their own subprocesses, so doing (for example): killall some_monitor1.sh will not always help.
Any other way to handle this situation?
pkill -P $$
will fit (just kills its own descendants)
And here is the help of -P
-P, --parent ppid,...
Only match processes whose parent process ID is listed.
and $$ is the process id of the script itself
After starting each child process, you can get its id with
ID=$!
Then you can use the stored PIDs to find and kill all grandchild etc. processes as described here or here.
If you use a negative PID with kill it will kill a process group. Example:
kill -- -1234
Extending pihentagy's answer to recursively kill all descendants (not just children):
kill_descendant_processes() {
local pid="$1"
local and_self="${2:-false}"
if children="$(pgrep -P "$pid")"; then
for child in $children; do
kill_descendant_processes "$child" true
done
fi
if [[ "$and_self" == true ]]; then
kill -9 "$pid"
fi
}
Now
kill_descendant_processes $$
will kill descedants of the current script/shell.
(Tested on Mac OS 10.9.5. Only depends on pgrep and kill)
kill $(jobs -p)
Rhys Ulerich's suggestion:
Caveat a race condition, using [code below] accomplishes what Jürgen suggested without causing an error when no jobs exist
[[ -z "$(jobs -p)" ]] || kill $(jobs -p)
pkill with optioin "-P" should help:
pkill -P $(pgrep some_monitor1.sh)
from man page:
-P ppid,...
Only match processes whose parent process ID is listed.
There are some discussions on linuxquests.org, please check:
http://www.linuxquestions.org/questions/programming-9/use-only-one-kill-to-kill-father-and-child-processes-665753/
I like the following straightforward approach: start the subprocesses with an environment variable with some name/value and use this to kill the subprocesses later. Most convenient is to use the process-id of the running bash script i.e. $$. This also works when subprocesses starts another subprocesses as the environment is inherited.
So start the subprocesses like this:
MY_SCRIPT_TOKEN=$$ some_monitor1.sh &
MY_SCRIPT_TOKEN=$$ some_monitor2.sh &
And afterwards kill them like this:
ps -Eef | grep "MY_SCRIPT_TOKEN=$$" | awk '{print $2}' | xargs kill
Similar to above, just a minor tweak to kill all processes indicated by ps:
ps -o pid= | tail -n +2 | xargs kill -9
Perhaps sloppy / fragile, but seemed to work at first blush. Relies on fact that current process ($$) tends to be first line.
Description of commands, in order:
Print PIDs for processes in current terminal, excl. header column
Start from Line 2 (excl. current terminal's shell)
Kill those procs
I've incorporated a bunch of the suggestions from the answers here into a single function. It gives time for processes to exit, murders them if they take too long, and doesn't have to grep through output (eg, via ps)
#!/bin/bash
# This function will kill all sub jobs.
function KillJobs() {
[[ -z "$(jobs -p)" ]] && return # no jobs to kill
local SIG="INT" # default to a gentle goodbye
[[ ! -z "$1" ]] && SIG="$1" # optionally send a different signal
# my version of 'kill' doesn't seem to understand `kill -- -${PID}`
#jobs -p | xargs -I%% kill -s "$SIG" -- -%% # kill each job's processes group
jobs -p | xargs kill -s "$SIG" # kill each job's processes group
## give the processes a moment to die, before forcing them to.
[[ "$SIG" != "KILL" ]] && {
sleep 0.2
KillJobs "KILL"
}
}
I also tried to get a variation working with pkill, but on my system (xubuntu 21.10) it does absolutely nothing.
#!/bin/bash
# This function doesn't seem to work.
function KillChildren() {
local SIG="INT" # default to a gentle goodbye
[[ ! -z "$1" ]] && SIG="$1" # optionally send a different signal
pkill --signal "$SIG" -P $$ # kill descendent's and their processes groups
[[ "$SIG" != "KILL" ]] && {
# give them a moment to die before we force them to.
sleep 0.2
KillChildren "KILL" ;
}
}

How do I make sure my bash script isn't already running?

I have a bash script I want to run every 5 minutes from cron... but there's a chance the previous run of the script isn't done yet... in this case, i want the new run to just exit. I don't want to rely on just a lock file in /tmp.. I want to make sure sure the process is actually running before i honor the lock file (or whatever)...
Here is what I have stolen from the internet so far... how do i smarten it up a bit? or is there a completely different way that's better?
if [ -f /tmp/mylockFile ] ; then
echo 'Script is still running'
else
echo 1 > /tmp/mylockFile
/* Do some stuff */
rm -f /tmp/mylockFile
fi
# Use a lockfile containing the pid of the running process
# If script crashes and leaves lockfile around, it will have a different pid so
# will not prevent script running again.
#
lf=/tmp/pidLockFile
# create empty lock file if none exists
cat /dev/null >> $lf
read lastPID < $lf
# if lastPID is not null and a process with that pid exists , exit
[ ! -z "$lastPID" -a -d /proc/$lastPID ] && exit
echo not running
# save my pid in the lock file
echo $$ > $lf
# sleep just to make testing easier
sleep 5
There is at least one race condition in this script. Don't use it for a life support system, lol. But it should work fine for your example, because your environment doesn't start two scripts simultaneously. There are lots of ways to use more atomic locks, but they generally depend on having a particular thing optionally installed, or work differently on NFS, etc...
You might want to have a look at the man page for the flock command, if you're lucky enough to get it on your distribution.
NAME
flock - Manage locks from shell scripts
SYNOPSIS
flock [-sxon] [-w timeout] lockfile [-c] command...
Never use a lock file always use a lock directory.
In your specific case, it's not so important because the start of the script is scheduled in 5min intervals. But if you ever reuse this code for a webserver cgi-script you are toast.
if mkdir /tmp/my_lock_dir 2>/dev/null
then
echo "running now the script"
sleep 10
rmdir /tmp/my_lock_dir
fi
This has a problem if you have a stale lock, means the lock is there but no associated process. Your cron will never run.
Why use a directory? Because mkdir is an atomic operation. Only one process at a time can create a directory, all other processes get an error. This even works across shared filesystems and probably even between different OS types.
Store your pid in mylockFile. When you need to check, look up ps for the process with the pid you read from file. If it exists, your script is running.
If you want to check the process's existence, just look at the output of
ps aux | grep your_script_name
If it's there, it's not dead...
As pointed out in the comments and other answers, using the PID stored in the lockfile is much safer and is the standard approach most apps take. I just do this because it's convenient and I almost never see the corner cases (e.g. editing the file when the cron executes) in practice.
If you use a lockfile, you should make sure that the lockfile is always removed. You can do this with 'trap':
if ( set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
echo "Locking succeeded" >&2
rm -f "$lockfile"
else
echo "Lock failed - exit" >&2
exit 1
fi
The noclobber option makes the creation of lockfile atomic, like using a directory.
As a one-liner and if you do not want to use a lockfile (e.g. b/c/ of a read only filesystem, etc)
test "$(pidof -x $(basename $0))" != $$ && exit
It checks that the full list of PID that bear the name of your script is equal to the current PID. The "-x" also checks for the name of shell scripts.
Bash makes it even shorter and faster:
[[ "$(pidof -x $(basename $0))" != $$ ]] && exit
In some cases, you might want to be able to distinguish between who is running the script and allow some concurrency but not all. In that case, you can use per-user, per-tty or cron-specific locks.
You can use environment variables such as $USER or the output of a program such as tty to create the filename. For cron, you can set a variable in the crontab file and test for it in your script.
you can use this one:
pgrep -f "/bin/\w*sh .*scriptname" | grep -vq $$ && exit
I was trying to solve this problem today and I came up with the below:
COMMAND_LINE="$0 $*"
JOBS=$(SUBSHELL_PID=$BASHPID; ps axo pid,command | grep "${COMMAND_LINE}" | grep -v $$ | g rep -v ${SUBSHELL_PID} | grep -v grep)
if [[ -z "${JOBS}" ]]
then
# not already running
else
# already running
fi
This relies on $BASHPID which contains the PID inside a subshell ($$ in the subshell is the parent pid). However, this relies on Bash v4 and I needed to run this on OSX which has Bash v3.2.48. I ultimately came up with another solution and it is cleaner:
JOBS=$(sh -c "ps axo pid,command | grep \"${COMMAND_LINE}\" | grep -v grep | grep -v $$")
You can always just:
if ps -e -o cmd | grep scriptname > /dev/null; then
exit
fi
But I like the lockfile myself, so I wouldn't do this without the lock file as well.
Since a socket solution has not yet been mentioned it is worth pointing out that sockets can be used as effective mutexes. Socket creation is an atomic operation, like mkdir is as Gunstick pointed out, so a socket is suitable to use as a lock or mutex.
Tim Kay's Perl script 'Solo' is a very small and effective script to make sure only one copy of a script can be run at any one time. It was designed specifically for use with cron jobs, although it works perfectly for other tasks as well and I've used it for non-crob jobs very effectively.
Solo has one advantage over the other techniques mentioned so far in that the check is done outside of the script you only want to run one copy of. If the script is already running then a second instance of that script will never even be started. This is as opposed to isolating a block of code inside the script which is protected by a lock. EDIT: If flock is used in a cron job, rather than from inside a script, then you can also use that to prevent a second instance of the script from starting - see example below.
Here's an example of how you might use it with cron:
*/5 * * * * solo -port=3801 /path/to/script.sh args args args
# "/path/to/script.sh args args args" is only called if no other instance of
# "/path/to/script.sh" is running, or more accurately if the socket on port 3801
# is not open. Distinct port numbers can be used for different programs so that
# if script_1.sh is running it does not prevent script_2.sh from starting, I've
# used the port range 3801 to 3810 without conflicts. For Linux non-root users
# the valid port range is 1024 to 65535 (0 to 1023 are reserved for root).
* * * * * solo -port=3802 /path/to/script_1.sh
* * * * * solo -port=3803 /path/to/script_2.sh
# Flock can also be used in cron jobs with a distinct lock path for different
# programs, in the example below script_3.sh will only be started if the one
# started a minute earlier has already finished.
* * * * * flock -n /tmp/path.to.lock -c /path/to/script_3.sh
Links:
Solo web page: http://timkay.com/solo/
Solo script: http://timkay.com/solo/solo
Hope this helps.
You can use this.
I'll just shamelessly copy-paste the solution here, as it is an answer for both questions (I would argue that it's actually a better fit for this question).
Usage
include sh_lock_functions.sh
init using sh_lock_init
lock using sh_acquire_lock
check lock using sh_check_lock
unlock using sh_remove_lock
Script File
sh_lock_functions.sh
#!/bin/bash
function sh_lock_init {
sh_lock_scriptName=$(basename $0)
sh_lock_dir="/tmp/${sh_lock_scriptName}.lock" #lock directory
sh_lock_file="${sh_lock_dir}/lockPid.txt" #lock file
}
function sh_acquire_lock {
if mkdir $sh_lock_dir 2>/dev/null; then #check for lock
echo "$sh_lock_scriptName lock acquired successfully.">&2
touch $sh_lock_file
echo $$ > $sh_lock_file # set current pid in lockFile
return 0
else
touch $sh_lock_file
read sh_lock_lastPID < $sh_lock_file
if [ ! -z "$sh_lock_lastPID" -a -d /proc/$sh_lock_lastPID ]; then # if lastPID is not null and a process with that pid exists
echo "$sh_lock_scriptName is already running.">&2
return 1
else
echo "$sh_lock_scriptName stopped during execution, reacquiring lock.">&2
echo $$ > $sh_lock_file # set current pid in lockFile
return 2
fi
fi
return 0
}
function sh_check_lock {
[[ ! -f $sh_lock_file ]] && echo "$sh_lock_scriptName lock file removed.">&2 && return 1
read sh_lock_lastPID < $sh_lock_file
[[ $sh_lock_lastPID -ne $$ ]] && echo "$sh_lock_scriptName lock file pid has changed.">&2 && return 2
echo "$sh_lock_scriptName lock still in place.">&2
return 0
}
function sh_remove_lock {
rm -r $sh_lock_dir
}
Usage example
sh_lock_usage_example.sh
#!/bin/bash
. /path/to/sh_lock_functions.sh # load sh lock functions
sh_lock_init || exit $?
sh_acquire_lock
lockStatus=$?
[[ $lockStatus -eq 1 ]] && exit $lockStatus
[[ $lockStatus -eq 2 ]] && echo "lock is set, do some resume from crash procedures";
#monitoring example
cnt=0
while sh_check_lock # loop while lock is in place
do
echo "$sh_scriptName running (pid $$)"
sleep 1
let cnt++
[[ $cnt -gt 5 ]] && break
done
#remove lock when process finished
sh_remove_lock || exit $?
exit 0
Features
Uses a combination of file, directory and process id to lock to make sure that the process is not already running
You can detect if the script stopped before lock removal (eg. process kill, shutdown, error etc.)
You can check the lock file, and use it to trigger a process shutdown when the lock is missing
Verbose, outputs error messages for easier debug

Resources