56

GNU bash, version 1.14.7(1)

I have a script is called "abc.sh" I have to check this from abc.sh script only... inside it I have written following statement

status=`ps -efww | grep -w "abc.sh" | grep -v grep | grep -v $$ | awk '{ print $2 }'`
if [ ! -z "$status" ]; then
        echo "[`date`] : abc.sh : Process is already running"
        exit 1;
fi

I know it's wrong because every time it exits as it found its own process in 'ps' how to solve it? how can I check that script is already running or not from that script only ?

codeforester
  • 39,467
  • 16
  • 112
  • 140
Jatin Bodarya
  • 1,425
  • 2
  • 20
  • 32
  • The PID of the running process is stored in $$, just ignore it from the list grep -v $$ – lc2817 May 29 '13 at 07:28
  • 1
    It is simpler to use some lock file. If file is present another copy is running. Just make sure the file is removed. – Grzegorz Żur May 29 '13 at 07:30
  • Its not safe way to check file in my case..so I suppose to use this way – Jatin Bodarya May 29 '13 at 07:36
  • What is the issue with this script? It seems to be correct, you are ignoring the PID of the current process and only checking if there is any other abc.sh process. – Adam Siemion May 29 '13 at 07:37
  • wrong is I suppose to check from this script only.... so every time It checks its own PID and exits !!! – Jatin Bodarya May 29 '13 at 07:39
  • @Grzegorz While on the surface a lock file may seem simpler, it isn't always so easy to use a lock file right, making sure that it always gets cleaned up correctly, no matter how the program died, or checking to see if it can be overridden when necessary... Getting it 100% right in all cases is actually rather difficult. Although, this is a shell script, so 90% may be downright acceptable... – twalberg May 29 '13 at 21:50
  • @twalberg I agree it might be troublesome. I think using pid file will be better, as we can check if it is valid even if it was not cleaned. My vote goes to pid file answer. – Grzegorz Żur May 29 '13 at 22:53
  • 2
    possible duplicate of [Quick-and-dirty way to ensure only one instance of a shell script is running at a time](http://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at) – user Jun 02 '14 at 11:28

17 Answers17

72

An easier way to check for a process already executing is the pidof command.

if pidof -x "abc.sh" >/dev/null; then
    echo "Process already running"
fi

Alternatively, have your script create a PID file when it executes. It's then a simple exercise of checking for the presence of the PID file to determine if the process is already running.

#!/bin/bash
# abc.sh

mypidfile=/var/run/abc.sh.pid

# Could add check for existence of mypidfile here if interlock is
# needed in the shell script itself.

# Ensure PID file is removed on program exit.
trap "rm -f -- '$mypidfile'" EXIT

# Create a file with current PID to indicate that process is running.
echo $$ > "$mypidfile"

...

Update: The question has now changed to check from the script itself. In this case, we would expect to always see at least one abc.sh running. If there is more than one abc.sh, then we know that process is still running. I'd still suggest use of the pidof command which would return 2 PIDs if the process was already running. You could use grep to filter out the current PID, loop in the shell or even revert to just counting PIDs with wc to detect multiple processes.

Here's an example:

#!/bin/bash

for pid in $(pidof -x abc.sh); do
    if [ $pid != $$ ]; then
        echo "[$(date)] : abc.sh : Process is already running with PID $pid"
        exit 1
    fi
done
Austin Phillips
  • 15,228
  • 2
  • 51
  • 50
  • 1
    and what if another script "xyzabc.sh" is also running at the same time ? This answer is good but not so safe. – Jatin Bodarya May 30 '13 at 05:11
  • 1
    @user95711 On my machine at least, `pidof` will not pick up `xyzabc.sh` if `pidof -x abc.sh` is run. If you want safe, you need a system wide lock resource which is why people are suggesting to use a PID file or unique directory name. Using the names in the process list isn't a good way to categorically check whether a process is running since the names can be spoofed. – Austin Phillips May 30 '13 at 05:30
  • 1
    @Pureferret See the Special Parameters section of the [bash man page](http://linux.die.net/man/1/bash) which states `$ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.` – Austin Phillips May 14 '15 at 23:08
  • `pidof` doesn't appear to be a standard tool. It is not available in my version of RedHat. – swdev Aug 17 '16 at 00:48
  • The update example code could be made more transportable by swapping the two instances of **abc.sh** with **$0**, so that no matter what bash script you drop it in it would know its own name. – frederickjh Jun 07 '18 at 08:19
  • 3
    @frederickjh Alternatively `$(basename $0)`: `for pid in $(pidof -x $(basename $0)); do` – Suuuehgi Nov 07 '19 at 17:40
  • If the script is started via a link, this won't work. – imrek Sep 08 '20 at 20:29
  • As @Suuuehgi ponts out, `basename` should be used, because a script may be run from any directory, but `$0` will be whatever path was specified, e.g. if `abc.sh` is in `~/programs/scripts`, running it first from that folder via `./abc.sh` and then again from `programs` via `scripts/abc.sh`, and a 3rd instance from ~ via `programs/scripts/abc.sh`, and so on..., will produce as many instances as many paths to the script you can come up with. – imrek Sep 08 '20 at 20:44
  • I tried this answer (the snippet), however, for some reasons, exiting the second instance exits the first instance too. Note that I need to match script name including all arguments, therefore I used `pgrep` instead. – tukusejssirs Dec 18 '20 at 20:24
  • This can be simplified by using the `-o` option: `if pidof -x -o $$ "${BASH_SOURCE[0]}"; then echo "Already running."; exit; fi` – yankee Feb 09 '22 at 09:31
39

I you want the "pidof" method, here is the trick:

    if pidof -o %PPID -x "abc.sh">/dev/null; then
        echo "Process already running"
    fi

Where the -o %PPID parameter tells to omit the pid of the calling shell or shell script. More info in the pidof man page.

Aba
  • 673
  • 6
  • 11
  • 1
    This seems the cleanest answer to me. – drgibbon Mar 30 '21 at 09:02
  • 1
    For /system/bin/sh, (Android default SHELL) I had to modify it using "-o $$", so my code looked like this ```if pidof -o $$ -x "$0" > /dev/null; then echo "Process already running"; fi``` Not sure why "-o %PPID" didn't work for me to exclude my script. – Vaibhav S Aug 02 '21 at 16:18
13

Working solution:

if [[ `pgrep -f $0` != "$$" ]]; then
        echo "Another instance of shell already exist! Exiting"
        exit
fi

Edit: I checked out some comments lately, so I tried attempting same with some debugging. I will also will explain it.

Explanation:

  • $0 gives filename of your running script.
  • $$ gives PID of your running script.
  • pgrep searches for process by name and returns PID.
  • pgrep -f $0 searches by filename, $0 being the current bash script filename and returns its PID.

So, pgrep checks if your script PID ($0) is equal to current running script ($$). If yes, then the script runs normally. If no, that means there's another PID with same filename running, so it exits. The reason I used pgrep -f $0 instead of pgrep bash is that you could have multiple instances of bash running and thus returns multiple PIDs. By filename, its returns only single PID.

Exceptions:

  1. Use bash script.sh not ./script.sh as it doesn't work unless you have shebang.
    • Fix: Use #!/bin/bash shebang at beginning.
  2. The reason sudo doesn't work is that it returns pgrep returns PID of both bash and sudo, instead of returning of of bash.
    • Fix:
#!/bin/bash
pseudopid="`pgrep -f $0 -l`"
actualpid="$(echo "$pseudopid" | grep -v 'sudo' | awk -F ' ' '{print $1}')"

if [[ `echo $actualpid` != "$$" ]]; then
    echo "Another instance of shell already exist! Exiting"
    exit
fi

while true
do
    echo "Running"
    sleep 100
done
  1. The script exits even if the script isn't running. That is because there's another process having that same filename. Try doing vim script.sh then running bash script.sh, it'll fail because of vim being opened with same filename
    • Fix: Use unique filename.
Machine Yadav
  • 196
  • 3
  • 13
11

Here's one trick you'll see in various places:

status=`ps -efww | grep -w "[a]bc.sh" | awk -vpid=$$ '$2 != pid { print $2 }'`
if [ ! -z "$status" ]; then
    echo "[`date`] : abc.sh : Process is already running"
    exit 1;
fi

The brackets around the [a] (or pick a different letter) prevent grep from finding itself. This makes the grep -v grep bit unnecessary. I also removed the grep -v $$ and fixed the awk part to accomplish the same thing.

twalberg
  • 59,951
  • 11
  • 89
  • 84
10

Someone please shoot me down if I'm wrong here

I understand that the mkdir operation is atomic, so you could create a lock directory

#!/bin/sh
lockdir=/tmp/AXgqg0lsoeykp9L9NZjIuaqvu7ANILL4foeqzpJcTs3YkwtiJ0
mkdir $lockdir  || {
    echo "lock directory exists. exiting"
    exit 1
}
# take pains to remove lock directory when script terminates
trap "rmdir $lockdir" EXIT INT KILL TERM

# rest of script here
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
  • 2
    This works but it leaves one in the position of not knowing if the process is actually running or not if it were to crash (potentially) ? – Mike Q May 20 '18 at 23:27
  • 1
    @MikeQ there's lots of additional steps we can do. Such as writing a pidfile in the directory, and if we can't create the dir because it exists, then verify the pid in the pidfile actually corresponds to a running instance of the program. – glenn jackman May 20 '18 at 23:34
  • Yeah, I briefly thought of that but wasn't sure how well it would work, I'm trying to get this to work for my script that checks vhosts domains and found the 'pgrep -f' mentioned (in another post) the best option so far. – Mike Q May 20 '18 at 23:38
  • 1
    You can't trap a KILL. – Tiana Nov 20 '19 at 20:08
9

Here's how I do it in a bash script:

if ps ax | grep $0 | grep -v $$ | grep bash | grep -v grep
then
    echo "The script is already running."
    exit 1
fi

This allows me to use this snippet for any bash script. I needed to grep bash because when using with cron, it creates another process that executes it using /bin/sh.

Rob
  • 26,989
  • 16
  • 82
  • 98
7

I find the answer from @Austin Phillips is spot on. One small improvement I'd do is to add -o (to ignore the pid of the script itself) and match for the script with basename (ie same code can be put into any script):

if pidof -x "`basename $0`" -o $$ >/dev/null; then
    echo "Process already running"
fi
Andrei Neagoe
  • 71
  • 1
  • 3
  • You could also use the special pid %PPID. From the pidof man page: The special pid %PPID can be used to name the parent process of the pidof program, in other words the calling shell or shell script. – Pat Mar 03 '21 at 07:40
  • Thanks, IMHO this is the neatest and most portable version – DAB Jan 27 '22 at 08:55
5

pidof wasn't working for me so I searched some more and came across pgrep

for pid in $(pgrep -f my_script.sh); do
    if [ $pid != $$ ]; then
        echo "[$(date)] : my_script.sh : Process is already running with PID $pid"
        exit 1
    else
      echo "Running with PID $pid"
    fi  
done

Taken in part from answers above and https://askubuntu.com/a/803106/802276

Scott H
  • 119
  • 2
  • 5
  • This was the only one that really worked for me, you have to jump through some start hoops (how you call the script) for the other methods and using a lock file makes it so when it crashes it won't start again due to the lock being there. – Mike Q May 20 '18 at 23:35
  • `pidof -x` failed for me on bash scripts, this however works seamlessly. – gomfy Mar 06 '20 at 16:25
4

Use the PS command in a little different way to ignore child process as well:

ps -eaf | grep -v grep | grep $PROCESS | grep -v $$
agold
  • 6,140
  • 9
  • 38
  • 54
2

I create a temporary file during execution.

This is how I do it:

#!/bin/sh
# check if lock file exists
if [ -e /tmp/script.lock ]; then
  echo "script is already running"
else
# create a lock file
  touch /tmp/script.lock
  echo "run script..."
#remove lock file
 rm /tmp/script.lock
fi
Cleber Reizen
  • 420
  • 4
  • 11
2

I have found that using backticks to capture command output into a variable, adversly, yeilds one too many ps aux results, e.g. for a single running instance of abc.sh:

ps aux | grep -w "abc.sh" | grep -v grep | wc -l

returns "1". However,

count=`ps aux | grep -w "abc.sh" | grep -v grep | wc -l`
echo $count

returns "2"

Seems like using the backtick construction somehow temporarily creates another process. Could be the reason why the topicstarter could not make this work. Just need to decrement the $count var.

Jimmy Falcon
  • 249
  • 2
  • 5
2

I didn't want to hardcode abc.sh in the check, so I used the following:

MY_SCRIPT_NAME=`basename "$0"`
if pidof -o %PPID -x $MY_SCRIPT_NAME > /dev/null; then
    echo "$MY_SCRIPT_NAME already running; exiting"
    exit 1
fi
Cloud Artisans
  • 4,016
  • 3
  • 30
  • 37
2

This is compact and universal

# exit if another instance of this script is running
for pid in $(pidof -x `basename $0`); do
   [ $pid != $$ ] && { exit 1; }
done
Francesco Gasparetto
  • 1,819
  • 16
  • 20
1

The cleanest fastest way:

processAlreadyRunning () {
    process="$(basename "${0}")"
    pidof -x "${process}" -o $$ &>/dev/null
}
0

For other variants (like AIX) that don't have pidof or pgrep. Reliability is greatly improved by getting a "static" view of the process table as opposed to piping it directly to grep. Setting IFS to null will preserve the carriage returns when the ps output is assigned to a variable.

#!/bin/ksh93

IFS=""
script_name=$(basename $0)
PSOUT="$(ps ax)"

ANY_TEXT=$(echo $PSOUT | grep $script_name | grep -vw $$ | grep $(basename $SHELL))

if [[ $ANY_TEXT ]]; then
   echo "Process is already running"
   echo "$ANY_TEXT"
   exit
fi
0

If the script is called from different locations, the $0 variable looks slightly different and because of that the above solutions did not work for me. I needed to combine basename and the [ ! -z "$var" ] tricks to get it to work.

#!/bin/bash

pid_of_other_process=`pidof -o $$ -x $(basename "$0")`

if [ ! -z "$pid_of_other_process" ]; then
   echo "The script is already running. Please terminate it first."
   echo It has PID "$pid_of_other_process"
   exit 1
else
    echo "This is the first instance of this script"
    # some ongoing process starts here
fi
Ecuashungo
  • 78
  • 12
-1

[ "$(pidof -x $(basename $0))" != $$ ] && exit

https://github.com/x-zhao/exit-if-bash-script-already-running/blob/master/script.sh