Skip to content
Linux Basics: Coordinate Shell Jobs with flock

Linux Basics: Coordinate Shell Jobs with flock

October 23, 2026

Two copies of a maintenance job can accidentally work on the same output at once. The util-linux flock command lets cooperating processes agree on a lock before entering that work. Practice command-scoped locking, an immediate conflict result, and shared readers without launching long-running background jobs.

These examples were tested on Linux with GNU Bash 5.2.37 and util-linux 2.41.5. Use Bash rather than sh. The commands operate only on the small practice inputs shown here.

Prepare a scratch directory

You need util-linux flock, Bash, a text editor, and the cat, mktemp, rm, and rmdir utilities. Keep using the same terminal. Save each named source file in this new directory, then run it once in the order shown. Invoking bash explicitly avoids changing executable permissions. No administrator access is needed.

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

1. Wrap a single command

Save this as flock-command.sh:

#!/usr/bin/env bash
set -eu
flock -n command.lock bash -c 'printf "%s\n" "snapshot complete" >> snapshot.log'
cat -- snapshot.log

Run it:

bash flock-command.sh

Output:

snapshot complete

The wrapper obtains an exclusive lock before starting the child Bash. That child appends one practice record, then exits; the lock is released after the command finishes. cat runs afterward and displays the result.

The separate command.lock is a coordination marker, not the report. Run this example once in the fresh directory so the displayed log contains exactly one record. The -n option refuses a conflicting lock immediately; without it, the default behavior is to wait.

2. Inspect a competing attempt

Save this as flock-conflict.sh:

#!/usr/bin/env bash
set -eu
exec 9>> conflict.lock
flock -n 9
if flock -n -E 75 conflict.lock printf 'Unexpected acquisition\n'; then
  exit 1
else
  status=$?
fi
printf 'Competing attempt: %s\n' "$status"
[ "$status" -eq 75 ] || exit 1
flock -u 9
flock -n conflict.lock printf 'After release: acquired\n'
exec 9>&-

Run it:

bash flock-conflict.sh

Output:

Competing attempt: 75
After release: acquired

Bash opens a marker as descriptor 9, and the first flock locks that open file. The next invocation separately opens the same pathname, creating a genuine competing attempt even though this script runs sequentially. It cannot acquire the exclusive lock.

The chosen conflict code 75 makes the expected branch explicit. After flock -u 9 releases the lock, another attempt succeeds. The final redirection closes the descriptor. Reserve your chosen conflict code so the protected command does not use it for a different failure.

3. Allow readers but exclude a writer

Save this as flock-readers.sh:

#!/usr/bin/env bash
set -eu
exec 9>> readers.lock
flock -s 9
flock -s -n readers.lock printf 'Second reader: acquired\n'
if flock -x -n -E 75 readers.lock printf 'Unexpected writer\n'; then
  exit 1
else
  status=$?
fi
printf 'Writer while reader holds lock: %s\n' "$status"
[ "$status" -eq 75 ] || exit 1
exec 9>&-
flock -x -n readers.lock printf 'Writer after reader closes: acquired\n'

Run it:

bash flock-readers.sh

Output:

Second reader: acquired
Writer while reader holds lock: 75
Writer after reader closes: acquired

The first descriptor holds a shared lock. A second shared reader succeeds, but an exclusive writer receives the expected conflict code. Closing descriptor 9 releases the remaining reader lock, allowing the final writer to run.

This is a coordination pattern, not a declaration that a program is harmless: a process holding a shared lock can still write if its code chooses to. Readers and writers must consistently request the appropriate lock mode.

Keep in mind

These locks coordinate cooperating programs; they do not make an unrelated writer obey the protocol. Use one agreed lock file on a filesystem with suitable locking support. Network filesystems such as NFS or CIFS can have different behavior or limitations.

An existing marker file does not mean its lock is currently held. Do not delete and recreate it while jobs might be using it: those processes may then lock different files behind the same pathname. Also avoid unintentionally passing a held descriptor to long-lived descendants, which can extend the lock lifetime. Clean up these markers only after all three practice scripts have finished.

Clean up

In the same terminal, remove only the named practice files, return to your original directory, and remove the now-empty scratch directory:

rm -- command.lock snapshot.log conflict.lock readers.lock flock-command.sh flock-conflict.sh flock-readers.sh
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

References

Acknowledgements

This post was created with assistance from AI.

Last updated on