Skip to content

Instantly share code, notes, and snippets.

@gdvalle
Last active May 23, 2021 18:26
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gdvalle/bf61b93e06a52e411da0 to your computer and use it in GitHub Desktop.
Save gdvalle/bf61b93e06a52e411da0 to your computer and use it in GitHub Desktop.
An atomic file updater in shell.
#!/bin/sh
set -u
PROG="${0##*/}"
# Default temp file suffix
SUFFIX="${SUFFIX-.tmp$$}"
usage() {
printf 'usage: echo foo | %s <file> [lockfile] [tempfile]\n' "$PROG" >&2
printf '\n'
printf 'This program performs the following:\n' >&2
printf ' - relays STDIN to tempfile (Default file%s\n' "$SUFFIX" >&2
printf ' - uses flock to ensure a single writer to file\n' >&2
printf ' - moves tempfile onto file\n' >&2
printf 'NOTE: Ensure file and tempfile are on the same filesystem\n' >&2
printf ' for an atomic mv operation!\n' >&2
}
lock() {
file="$1"
lock_file="${2-${file}.lock}"
# Create lock file with FD<10 for POSIX compatibility
exec 9>"$lock_file"
# Acquire lock
flock -n 9 \
&& return 0 \
|| return 1
}
main() {
if [ $# -lt 1 ]; then
usage
return 1
fi
file=$1
lock_file="${2-${file}.lock}"
temp_file="${3-${file}${SUFFIX}}"
# Get the lock
lock "$file" "$lock_file" \
|| return 2
# Deferred cleanup until program exit.
# Remove lock file if process is interrupted.
trap 'rm -f "$lock_file"' 1 2 3 6 15
# Temp file is always removed.
trap 'rm -f "$temp_file"' EXIT
# Relay STDIN to a temp file
cat > "$temp_file"
if [ "$?" -ne 0 ]; then
printf "Failed writing to %s\n" "$temp_file" >&2
return 3
fi
# Move temp file into place, hopefully atomically
mv "$temp_file" "$file"
if [ "$?" -ne 0 ]; then
printf "Failed moving %s to %s\n" "$temp_file" "$file" >&2
return 4
fi
rm "$lock_file"
}
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment