Skip to content

Instantly share code, notes, and snippets.

@kfiresmith
Created August 30, 2024 19:53
Show Gist options
  • Save kfiresmith/f553bab0bad409166e2f34eec91e5bbd to your computer and use it in GitHub Desktop.
Save kfiresmith/f553bab0bad409166e2f34eec91e5bbd to your computer and use it in GitHub Desktop.
Setting a PID file / lock file to prevent duplicate script runs (bash)
#!/bin/bash
# Obtain the name of the script dynamically so that we can re-use this code block on any script
SCRIPT_FULLNAME="${0##*/}"
# Trim off the file extension if one is present
SCRIPT_TRIMMEDNAME="${SCRIPT_FULLNAME%.*}"
# /run is superior to /tmp, but we can't write a lock file there if we aren't root.
# We want to maintain the option of using this code block in scripts not run by root,
# so we fall back to using good old /tmp
LOCK_DIR="/run"
[ -w "$LOCK_DIR" ] || LOCK_DIR="/tmp"
LOCK_FILE="$LOCK_DIR/$SCRIPT_TRIMMEDNAME.lock"
# Function to remove the lock file on exit
cleanup() {
rm -f "$LOCK_FILE"
}
# Trap to ensure PID file is removed even if the script is interrupted
# This runs on exit whether the script was successful, failed, or interrupted.
trap cleanup EXIT
# Check if the PID file exists. If it already exists, we just exit so that we don't
# create a cron pileup by re-running the running script again over itself.
if [ -e "$LOCK_FILE" ]; then
echo "Script is already running. Exiting."
exit 1
fi
# If we've made it this far, write the current PID to the PID file. Now we can
# proceed with running the actual script
echo $$ > "$LOCK_FILE"
# Do stuff below
# Remove the PID file (handled by the trap)
exit 0
@kfiresmith
Copy link
Author

Feedback welcome!

@kfiresmith
Copy link
Author

In Python:

#!/usr/bin/env python3

import os
import sys
import atexit

# Obtain the name of the script dynamically so that we can re-use this code block on any script
script_fullname = os.path.basename(__file__)
# Trim off the file extension if one is present
script_trimmedname = os.path.splitext(script_fullname)[0]

# /run is superior to /tmp, but we can't write a lock file there if we aren't root.
# We want to maintain the option of using this code block in scripts not run by root,
# so we fall back to using good old /tmp
lock_dir = "/run" if os.access("/run", os.W_OK) else "/tmp"
lock_file = os.path.join(lock_dir, f"{script_trimmedname}.lock")

def cleanup():
    """Remove the lock file on exit."""
    if os.path.exists(lock_file):
        os.remove(lock_file)

# Register the cleanup function to run on script exit
atexit.register(cleanup)

# Check if the lock file exists. If it already exists, we just exit to prevent
# creating a cron pileup by re-running the running script over itself.
if os.path.exists(lock_file):
    print("Script is already running. Exiting.")
    sys.exit(1)

# If we've made it this far, write the current PID to the lock file.
with open(lock_file, 'w') as f:
    f.write(str(os.getpid()))

# Do stuff below

# Exit (cleanup will be handled by atexit)
sys.exit(0)

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