| |

Bash Scripting 28 ๐Ÿง File Locking โ€” Preventing duplicate script execution with flock

โฑ๏ธ Estimated read: 14 min | |

A script that runs from cron, a systemd timer, or a user’s terminal can be started again before the previous run has finished. When two instances of the same script run simultaneously, they write to the same files, use the same temporary directories, and compete for the same resources. The result is corrupted output, duplicated work, or a race condition that is difficult to reproduce. The flock command is the standard Linux tool for preventing this. It acquires an advisory lock on a file, and any other process that tries to acquire the same lock either waits or fails immediately, depending on the options.

This chapter covers the two ways to use flock in Bash scripts: wrapping the entire script with the external flock command, and using flock inside the script with a file descriptor. It explains the difference between blocking and non-blocking modes, the timeout option, the self-locking boilerplate that uses the script file itself as the lock, and the pitfall where a killed script leaves the lock held by a child process.

Key point: flock locks are advisory and tied to the open file description, not the file path. The kernel releases the lock automatically when the file descriptor is closed, which happens when the process exits or is killed. This is why flock is safer than a hand-rolled PID file: a crashed script cannot leave a stale lock behind.


Why flock exists

The PID file race problem. A common but flawed approach to preventing duplicate execution is to check for a PID file and create one if it does not exist. The check and the creation are two separate operations, and another process can start between them, see that the file does not exist, and create its own PID file. Both processes then believe they have the lock. The flock system call performs the lock acquisition atomically. There is no window between the check and the acquisition .

The stale lock problem. A PID file can become stale if the script is killed before it removes the file. The next run sees the file, checks the PID, finds that no process with that PID exists, and must decide whether to override the lock. This logic is error-prone, and PIDs are reused, so a stale PID file can point to an unrelated process. flock avoids this entirely: the lock is held by an open file descriptor, and the kernel releases it when the descriptor is closed, regardless of how the process ended .

The advisory lock distinction. flock locks are advisory. They do not prevent another process from opening or writing to the locked file unless that process also calls flock. The lock is a convention between cooperating processes. For the purpose of preventing duplicate script execution, this is sufficient: every instance of the script follows the same convention and attempts to acquire the same lock .


a. Wrapping the script with flock

The simplest way to use flock is to wrap the script invocation in the flock command. The command acquires the lock, runs the script, and releases the lock when the script exits.

# Run the script with an exclusive lock
flock /var/lock/myscript.lock /path/to/myscript.sh

If another instance of the script is already running, flock waits for the lock to be released before starting the new instance. This is the blocking behavior, and it is the default. For a cron job, waiting is usually undesirable: if the previous run is still going, the new run should be skipped, not queued. The -n (non-blocking) option makes flock fail immediately if the lock cannot be acquired .

# In a crontab: skip the run if the lock is held
*/5 * * * * /usr/bin/flock -n /var/lock/myscript.lock /path/to/myscript.sh

When the lock is held, flock exits with status 1 (or the value specified by -E) and does not run the command . The cron job completes without doing anything, and the next scheduled run will try again.

For a bounded wait, the -w option specifies a timeout in seconds. Decimal fractional values are accepted .

# Wait up to 30 seconds for the lock
flock -w 30 /var/lock/myscript.lock /path/to/myscript.sh

If the lock is not acquired within the timeout, flock exits with a failure status and does not run the command.


b. Using flock inside the script

The external flock command is convenient for cron jobs, but it does not help when the script is invoked in different ways, or when the script needs to log the fact that it was skipped. The alternative is to use flock inside the script with a file descriptor.

The pattern is to open a file descriptor to the lock file in a subshell, call flock on that descriptor, and exit if the lock is not acquired.

#!/bin/bash

(
    flock -n 200 || {
        echo "Another instance is running" >&2
        exit 1
    }

    # All the script's work goes here
    echo "Running with PID $$"
    sleep 60

) 200>/var/lock/myscript.lock

The redirection 200>/var/lock/myscript.lock opens file descriptor 200 for writing to the lock file. The flock -n 200 attempts to acquire an exclusive lock on that descriptor. If the lock is already held, flock returns non-zero, and the || branch exits the subshell with status 1 .

The lock is released when the subshell exits, because the file descriptor is closed at that point. The lock file itself remains on disk, but its existence does not mean the lock is held. The lock is a property of the open file description, not the file’s presence .

A more structured version defines a function for the failure case:

#!/bin/bash

another_instance() {
    echo "There is another instance running, exiting" >&2
    exit 1
}

(
    flock -n 100 || another_instance

    # Script work here
    DEST="/backup/$(date +%s)"
    mkdir -p "$DEST"
    rsync -avz root@myhost:/home/web "$DEST/."

) 100>/var/lock/dobackup.lock

This is the pattern from the Baeldung example, and it is the most common way to embed locking inside a script .


c. Self-locking boilerplate

A more elegant approach uses the script file itself as the lock file. This avoids a separate lock file and ensures that the lock is always associated with the script’s own path. The boilerplate is placed at the top of the script.

#!/bin/bash

# Self-locking boilerplate
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

The logic is: if the FLOCKER environment variable is not set to the script’s path, re-execute the script under flock, with FLOCKER set to the script’s path. The flock command acquires an exclusive lock on the script file itself (-e for exclusive, -n for non-blocking), then runs the script again. On the second execution, FLOCKER is set to $0, so the condition is false, and the script proceeds to its actual work .

The -e flag is the default (exclusive lock) and can be omitted. The -n flag makes the lock non-blocking. If the script is already running, the second invocation exits immediately with a non-zero status.

This boilerplate has the advantage of being a single line that can be copied into any script. It does not require a separate lock file, and it works regardless of how the script is invoked.


d. The killed-script pitfall

A known issue with the subshell pattern is that if the script is killed with SIGTERM, the lock may remain held by a child process. The flock lock is associated with the open file description, and file descriptors are inherited by child processes. If the script starts a long-running background process, that child inherits the lock descriptor and keeps the lock alive even after the script exits .

The fix is to ensure that child processes do not inherit the lock descriptor, or to kill the process group when the script is terminated.

#!/bin/bash
set -e

# Ensure the whole process group is killed on termination
trap 'kill 0' SIGINT SIGTERM

exec 7<"$0"
flock -n 7

# Work here
sleep 60

The trap 'kill 0' SIGINT SIGTERM sends the signal to the entire process group, including any background children. This ensures that the lock is released when the script is terminated, because all processes holding the descriptor are killed .

An alternative is to use the -o (close) option with the external flock command, which closes the lock file descriptor before running the command. This prevents the command’s children from inheriting the lock, but it also means the lock is not held for the duration of the command, which defeats the purpose. The -o option is useful when the command itself spawns a background process that should not hold the lock .


Complete Example Session

# ============================================
# PART 1: THE SIMPLE WRAPPER
# ============================================
flock /var/lock/myscript.lock /path/to/myscript.sh

# Blocks until the lock is available.
# Runs the script while holding the lock.
# Releases the lock when the script exits.
# ============================================
# PART 2: NON-BLOCKING WRAPPER
# ============================================
flock -n /var/lock/myscript.lock /path/to/myscript.sh

# Exits immediately with status 1 if the lock is held.
# Does not run the script.
# ============================================
# PART 3: TIMEOUT WRAPPER
# ============================================
flock -w 30 /var/lock/myscript.lock /path/to/myscript.sh

# Waits up to 30 seconds for the lock.
# Exits with failure if the timeout is reached.
# ============================================
# PART 4: CRON JOB WITH LOCK
# ============================================
# In crontab:
*/5 * * * * /usr/bin/flock -n /var/lock/backup.lock /usr/local/bin/backup.sh

# The cron job skips the run if the previous one is still going.
# ============================================
# PART 5: IN-SCRIPT LOCKING
# ============================================
#!/bin/bash

(
    flock -n 200 || {
        echo "Another instance is running" >&2
        exit 1
    }

    echo "Running with PID $$"
    sleep 60

) 200>/var/lock/myscript.lock
# ============================================
# PART 6: THE FUNCTION PATTERN
# ============================================
#!/bin/bash

another_instance() {
    echo "Another instance is running" >&2
    exit 1
}

(
    flock -n 100 || another_instance
    # Work here
) 100>/var/lock/dobackup.lock
# ============================================
# PART 7: SELF-LOCKING BOILERPLATE
# ============================================
#!/bin/bash
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

# The script locks itself on the first run.
# The second run exits immediately.
# ============================================
# PART 8: THE KILLED-SCRIPT PITFALL
# ============================================
#!/bin/bash
set -e

trap 'kill 0' SIGINT SIGTERM

exec 7<"$0"
flock -n 7

sleep 60
# ============================================
# PART 9: VERIFY THE LOCK
# ============================================
# Start the script in one terminal
./myscript.sh

# In another terminal, try to start it again
./myscript.sh
# Output: Another instance is running
# Exit code: 1
# ============================================
# PART 10: CHECK THE LOCK FILE
# ============================================
ls -l /var/lock/myscript.lock

# The file exists, but its existence does not mean the lock is held.
# The lock is held only while a process has the file open.

The ten parts covered the simple wrapper, non-blocking mode, timeout, the cron pattern, in-script locking, the function pattern, the self-locking boilerplate, the killed-script pitfall, verification, and the lock file’s existence.


Quick Reference

flock command forms

FormExamplePurpose
Wrap commandflock file commandLock around a command
In-script( flock -n 200 || exit 1; ... ) 200>fileLock inside the script
File descriptorflock -n 200Lock an existing descriptor
Self-lockingexec env FLOCKER="$0" flock -en "$0" "$0" "$@"Script locks itself

Options

OptionEffect
-nNon-blocking, fail if lock held
-w secondsWait up to seconds
-eExclusive lock (default)
-sShared lock
-E numberExit code on lock failure
-oClose descriptor before running command

Exit codes

CodeMeaning
0Lock acquired, command succeeded
1Lock not acquired (with -n or -w)
OtherCommand’s exit status

Best practices

PracticeReason
Use -n for cronSkip instead of queue
Use trap 'kill 0'Release lock on termination
Use /var/lock or /var/libPersistent lock location
Do not delete the lock fileExistence does not mean held

Best Practices

โœ… Do This:

# Use -n for cron jobs to skip overlapping runs
flock -n /var/lock/job.lock /path/to/job.sh                  # โœ…
# Use a dedicated lock directory
mkdir -p /var/lock/myapp && flock -n /var/lock/myapp/job.lock # โœ…
# Trap termination to release the lock
trap 'kill 0' SIGINT SIGTERM                                 # โœ…
# Use the self-locking boilerplate for portability
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || : # โœ…

โŒ Don’t Do This:

# Use a PID file without atomic locking
if [ -f /var/run/job.pid ]; then exit 1; fi; echo $$ > /var/run/job.pid # โŒ (race)
# Delete the lock file to "clear" a lock
rm /var/lock/job.lock                                        # โŒ (does not release)
# Use a shared lock for mutual exclusion
flock -s /var/lock/job.lock /path/to/job.sh                  # โŒ (multiple shared locks allowed)
# Forget that child processes inherit the lock descriptor
long_running_task &  # inside the flock subshell             # โŒ (lock held by child)

Common Pitfalls

PitfallWhy It HappensFix
Lock held by killed script’s childChild inherited the descriptortrap 'kill 0' SIGINT SIGTERM
Lock file deleted but lock still heldLock is on the open file, not the pathDo not delete; wait for process exit
Cron job queues instead of skippingNo -n flagUse flock -n
Lock not releasedSubshell not used, descriptor not closedUse the ( ... ) 200>file pattern
Self-locking boilerplate fails$0 is not the script pathUse an absolute path or realpath "$0"

Real-World Examples

1. Simple lock wrapper

flock /var/lock/job.lock /path/to/job.sh

2. Non-blocking lock wrapper

flock -n /var/lock/job.lock /path/to/job.sh

3. Timeout lock wrapper

flock -w 60 /var/lock/job.lock /path/to/job.sh

4. Cron job with lock

*/5 * * * * flock -n /var/lock/job.lock /path/to/job.sh

5. In-script lock

( flock -n 200 || exit 1; work ) 200>/var/lock/job.lock

6. Function-based failure

another_instance() { echo "Already running" >&2; exit 1; }
( flock -n 100 || another_instance; work ) 100>/var/lock/job.lock

7. Self-locking boilerplate

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

8. Trap for cleanup

trap 'kill 0' SIGINT SIGTERM

9. Exclusive lock

flock -e /var/lock/job.lock /path/to/job.sh

10. Check lock status

lslocks | grep job.lock

Visual

The wrapper vs in-script pattern

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  WRAPPER                                                     โ”‚
โ”‚                                                              โ”‚
โ”‚  flock -n /var/lock/job.lock ./job.sh                        โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ”œโ”€โ”€ Lock acquired โ”€โ”€โ–บ run job.sh โ”€โ”€โ–บ release           โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ””โ”€โ”€ Lock held โ”€โ”€โ–บ exit 1 (job not run)                 โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚
โ”‚                                                              โ”‚
โ”‚  IN-SCRIPT                                                   โ”‚
โ”‚                                                              โ”‚
โ”‚  (                                                           โ”‚
โ”‚    flock -n 200 || exit 1                                    โ”‚
โ”‚    # work                                                    โ”‚
โ”‚  ) 200>/var/lock/job.lock                                    โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ”œโ”€โ”€ fd 200 opened to lock file                         โ”‚
โ”‚       โ”œโ”€โ”€ flock attempts lock on fd 200                      โ”‚
โ”‚       โ””โ”€โ”€ subshell exits โ”€โ”€โ–บ fd 200 closed โ”€โ”€โ–บ lock released โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Blocking vs non-blocking

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  BLOCKING (default)                                          โ”‚
โ”‚                                                              โ”‚
โ”‚  Process A holds lock                                        โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Process B calls flock                                       โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Process B WAITS until A releases                            โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Process B acquires lock and runs                            โ”‚
โ”‚                                                              โ”‚
โ”‚  โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚
โ”‚                                                              โ”‚
โ”‚  NON-BLOCKING (-n)                                           โ”‚
โ”‚                                                              โ”‚
โ”‚  Process A holds lock                                        โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Process B calls flock -n                                    โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Process B exits immediately with status 1                   โ”‚
โ”‚  (does not run)                                              โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

The killed-script pitfall

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  WITHOUT TRAP                                                โ”‚
โ”‚                                                              โ”‚
โ”‚  Script holds lock                                           โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ”œโ”€โ”€ starts background child                            โ”‚
โ”‚       โ”‚       โ”‚                                              โ”‚
โ”‚       โ”‚       โ””โ”€โ”€ inherits lock descriptor                   โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Script killed with SIGTERM                                  โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Script exits, but child still holds descriptor              โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Lock remains held until child exits                         โ”‚
โ”‚                                                              โ”‚
โ”‚  WITH TRAP 'kill 0'                                          โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  SIGTERM triggers trap                                       โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  kill 0 sends signal to entire process group                โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  All processes exit, descriptor closed, lock released       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Self-locking flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  SELF-LOCKING BOILERPLATE                                    โ”‚
โ”‚                                                              โ”‚
โ”‚  First run:                                                  โ”‚
โ”‚    FLOCKER not set โ”€โ”€โ–บ exec flock -en "$0" "$0" "$@"        โ”‚
โ”‚       โ”‚                                                      โ”‚
โ”‚       โ–ผ                                                      โ”‚
โ”‚  Second run (under flock):                                   โ”‚
โ”‚    FLOCKER="$0" โ”€โ”€โ–บ condition false โ”€โ”€โ–บ proceed to work     โ”‚
โ”‚                                                              โ”‚
โ”‚  Concurrent run:                                             โ”‚
โ”‚    flock fails (non-blocking) โ”€โ”€โ–บ exits immediately          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Summary

ItemValue
System callflock(2)
Commandflock (util-linux)
Lock typeAdvisory, exclusive by default
Non-blocking-n
Timeout-w seconds
In-script fdflock -n FD with FD>file redirection
Lock releaseAutomatic on descriptor close
Self-lockingexec env FLOCKER="$0" flock -en "$0" "$0" "$@"
Killed-script fixtrap 'kill 0' SIGINT SIGTERM
Lock file existenceDoes not indicate lock is held

Key takeaways:

  • flock is safer than a PID file. The lock is atomic and released automatically when the process exits or is killed. A PID file requires a race-prone check-and-create sequence and can become stale .
  • Use -n for cron jobs. A scheduled run that finds the lock held should skip, not queue. The -n flag makes flock exit immediately with status 1 instead of waiting .
  • The in-script pattern uses a file descriptor. ( flock -n 200 || exit 1; work ) 200>/var/lock/file locks the descriptor, runs the work in a subshell, and releases the lock when the subshell exits .
  • Child processes inherit the lock descriptor. If the script starts a background process, that process keeps the lock alive after the script exits. The trap 'kill 0' SIGINT SIGTERM pattern kills the entire process group to release the lock .
  • The self-locking boilerplate is the most portable pattern. It uses the script file itself as the lock file, re-executes under flock on the first run, and proceeds on the second. It requires no separate lock file management .
  • Do not delete the lock file to “clear” a lock. The lock is on the open file description, not the path. Deleting the file does not release the lock and can create a race where a new file is locked by a different process .

Remember: The purpose of flock is to serialize access to a resource that cannot be used concurrently. The lock is advisory, so every instance of the script must follow the same convention. The wrapper pattern is simplest for cron jobs. The in-script pattern gives the script control over logging and exit codes. The self-locking boilerplate is the most portable. Whichever pattern you choose, be aware of the child-process inheritance problem: a lock held by a killed script’s child is a lock that never releases. Use trap 'kill 0' to ensure the entire process group terminates, and never delete the lock file to force a release.

Was this helpful?


Stop using slow, ad-bloated tool sites! ๐Ÿคฎ

๐Ÿ”Ž Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
โœ… Finance (Mortgage, Interest, Inflation)
โœ… Tech (Base64, JSON, Dev Suite, IP)
โœ… Health (BMI, BMR, TDEE)
โœ… Productivity (Timer, Workspace, QR)

โšก๏ธ Fast & Private
๐Ÿ”’ No data leaves your device
๐Ÿ’Ž 100% Free

๐Ÿ”— Use it now: https://tools.kandz.me
๐Ÿ”– Bookmark itโ€”youโ€™ll need it later!