57

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 ?

8
  • The PID of the running process is stored in $$, just ignore it from the list grep -v $$ Commented May 29, 2013 at 7: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. Commented May 29, 2013 at 7:30
  • Its not safe way to check file in my case..so I suppose to use this way Commented May 29, 2013 at 7: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. Commented May 29, 2013 at 7:37
  • 2
    possible duplicate of Quick-and-dirty way to ensure only one instance of a shell script is running at a time Commented Jun 2, 2014 at 11:28

18 Answers 18

75

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
Sign up to request clarification or add additional context in comments.

10 Comments

and what if another script "xyzabc.sh" is also running at the same time ? This answer is good but not so safe.
@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.
@Pureferret See the Special Parameters section of the bash man page 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.
pidof doesn't appear to be a standard tool. It is not available in my version of RedHat.
@frederickjh Alternatively $(basename $0): for pid in $(pidof -x $(basename $0)); do
|
46

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.

2 Comments

This seems the cleanest answer to me.
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.
15

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.

4 Comments

This is the best solution. Can't understand, why this isn' not valued with likes.
Hmm, this doesn't work when I sudo, why is that?
I used this solution to ensure that a cron job does not start a script if it is already running. Unfortunately, if somebody has the script open in vi, the condition also prevents another execution of the script ;-)
doesn't seem to work for me on mac os running from zsh prompt
11

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

4 Comments

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) ?
@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.
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.
You can't trap a KILL.
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.

Comments

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.

1 Comment

This is the only solution that I found that works on Mac OS X.
8

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

2 Comments

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.
Thanks, IMHO this is the neatest and most portable version
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

2 Comments

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.
pidof -x failed for me on bash scripts, this however works seamlessly.
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 $$

Comments

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

Comments

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.

Comments

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

Comments

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

2 Comments

no pidof command on macos
no mention of macos in the question
1

The cleanest fastest way:

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

Comments

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

Comments

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

Comments

0

You can use flock to prevent multiple instances of a shell script from running simultaneously.

#!/usr/bin/env bash

# Ensure exclusive lock using flock
: "${FLOCKER:=-}"
if [ "${FLOCKER}" != "$0" ]; then
    exec env FLOCKER="$0" flock -en "$0" "$0" "$@"
else
    :
fi

Explanation:

  1. The script is designed to ensure that only one instance of the script can be running at a time. It does so by using a file-based lock mechanism provided by the flock command.
  2. The FLOCKER variable is used to track whether the script is already running. If FLOCKER is not equal to the script's name ($0), it indicates that the script is not already running, and it attempts to acquire an exclusive lock.
  3. The env FLOCKER="$0" flock -en "$0" "$0" "$@" command is responsible for acquiring the lock and executing the script. If the lock cannot be acquired, the script exits gracefully.

The use of : (colon) in the no-op action provides a clear indication that no additional actions are intended when the script is already running.

Limitations: flock is not POSIX compliant.

1 Comment

will it reliably work in case $0 = "abc.sh" and then another instance is run as "/fullpath/abc.sh" ?
-1

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

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.