Skip to content

Instantly share code, notes, and snippets.

@JackZielke
Created February 19, 2022 22:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JackZielke/e00270c83ebac0e702eb00eaf122ac29 to your computer and use it in GitHub Desktop.
Save JackZielke/e00270c83ebac0e702eb00eaf122ac29 to your computer and use it in GitHub Desktop.
Run one copy of this script - oneliner
#!/bin/dash
# Is this script already running?
pidof -x $(basename $0) -o $$ >/dev/null && exit
@JackZielke
Copy link
Author

Summary

I have added code to several scripts that tries to ensure that only 1 copy of the script is running at a time. I usually do this to cron jobs that have an indeterminate run length. The last time I needed to do this I took the time to write generic code that could be pasted into any bash script.

The Code

# only run 1 copy
if [ "$pidof -x -o $$ $(basename $0))" != "" ]; then exit fi

This was later shortened to
pidof -x $(basename $0) -o $$ >/dev/null && exit

The description below was written for the original version but applies to the current version pretty well.

Problems

Each script that contains this code must have a unique name.
This does not detect if the duplicate copy is running as the same user as the script that is checking.

How it works

This piece of code runs two external programs:
pidof (link to killall5)
basename

In Ubuntu, pidof is included with sysvinit-utils and basename is in coreutils.

basename
Let's look at basename first. The man page says:

basename - strip directory and suffix from filenames

Examples
basename /usr/bin/sort
Output "sort".
This program is going to turn the full path into just the filename. The bash man page provides information about two useful variables under the Special Parameters section:
0 Expands to the name of the shell or shell script. This is set at shell initialization. If bash is invoked with a file of commands, $0 is set to the name of that file. If bash is started with the -c option, then $0 is set to the first argument after the string to be executed, if one is present. Otherwise, it is set to the file name used to invoke bash, as given by argument zero.
$ Expands to the process ID of the shell. In a () subshell, it expands to the process ID of the current shell, not the subshell.
So normally $0 is 'bash' but when we are running a script $0 is the path to the script that is running. If we couple that with basename and some Command Substitution $() we end up with the script name being passed as a parameter to pidof.

pidof
Now let's see what we are doing with pidof. Again the man page has some useful information.

-x Scripts too - this causes the program to also return process id's of shells running the named scripts.
-o Tells pidof to omit processes with that process id. 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.
We use -x so that we can search by the script name. When you omit the -x pidof will search for binary names only. For this script that would be 'bash'. That would not be useful here.

-o allows us to omit a PID. By using the $$ variable (mentioned in the section of the bash man page quoted above) the PID of the current script will be used as an argument to -o.

Our final question to pidof looks something like: Are there any scripts, other than myself, running with the same name? pidof returns PIDs separated by spaces. If it returns any output at all then there is already a script running with the same name other than ourselves. In that case the script will exit.

You could have an error displayed on the screen or log an error or something but I think in most cases just halting the execution of the script is enough.

The new version uses the exit status from pidof, and the control operator && used in the command list.

EXIT STATUS
0 At least one program was found with the requested name.
1 No program was found with the requested name.

  AND and OR lists are sequences of one or more  pipelines  separated  by
  the  &&  and  || control operators, respectively.  AND and OR lists are
  executed with left associativity.  An AND list has the form

         command1 && command2

@JackZielke
Copy link
Author

Archive.org version of the original page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment