#!/bin/bash # swap - swap two files by renaming via a temp file # # Usage: # swap # # Options: # -h, --help Show help message _swap() ( local SCRIPT_NAME; SCRIPT_NAME="$(basename "${BASH_SOURCE[0]}")" case "${BASH_SOURCE[0]}" in /dev/*|/proc/*) SCRIPT_NAME="" ;; esac case "$SCRIPT_NAME" in ""|bash|sh|zsh|dash) SCRIPT_NAME="swap" ;; esac _show_help() { local s; [ -t 1 ] && s=$'\033[4m' local r; [ -t 1 ] && r=$'\033[24m' echo "NAME" echo " $SCRIPT_NAME - swap two files by renaming" echo "SYNOPSIS" echo " $SCRIPT_NAME ${s}file1${r} ${s}file2${r}" echo " $SCRIPT_NAME -h" echo "DESCRIPTION" echo " Atomically swaps two files using mv and a temp file at" echo " /tmp/temp-swap-${s}basename${r} (falls back to" echo " /tmp/temp-swap-${s}basename${r}-\$\$-\$RANDOM on collision)." echo " Directories are not supported. On partial failure," echo " attempts to restore the original state (see EXIT STATUS)." echo "EXIT STATUS" echo " 0 Success" echo " 1 Not enough arguments" echo " 2 First file does not exist" echo " 3 First file is a directory" echo " 4 Second file does not exist" echo " 5 Second file is a directory" echo " 6 Failed to move first file to temp (no restore needed: files untouched)" echo " 7 Failed to move second file to first (restore attempted: temp -> first)" echo " 8 Failed to move temp to second (no restore attempted: second already moved)" } _error() { echo "[ERR][$SCRIPT_NAME] $*" >&2; } case "$1" in -h|--help) _show_help return 0 ;; esac if [ $# -lt 2 ]; then _error "Must provide two files to swap (received $#). Run \`$SCRIPT_NAME -h\` for usage" return 1 fi local file1="$1" local file2="$2" # Validate first file if [ ! -e "$file1" ]; then _error "File '$file1' does not exist" return 2 elif [ -d "$file1" ]; then _error "'$file1' is a directory" return 3 fi # Validate second file if [ ! -e "$file2" ]; then _error "File '$file2' does not exist" return 4 elif [ -d "$file2" ]; then _error "'$file2' is a directory" return 5 fi # Create a unique temporary filename for swapping local basename; basename="$(basename "$file1")" local temp_file="/tmp/temp-swap-$basename" while [ -e "$temp_file" ]; do temp_file="/tmp/temp-swap-$basename-$$-$RANDOM" done # Perform the swap using `mv`. Return distinct codes for each failure point if ! mv "$file1" "$temp_file"; then # file1 -> temp _error "Failed to move '$file1' to temporary file '$temp_file'" return 6 fi if ! mv "$file2" "$file1"; then # file2 -> file1 _error "Failed to move '$file2' to '$file1'" # Move failed - Attempt to restore the original file if ! mv "$temp_file" "$file1"; then _error "Failed to restore '$temp_file' to '$file1'" fi return 7 fi if ! mv "$temp_file" "$file2"; then # temp -> file2 _error "Failed to move temporary file to '$file2'" return 8 fi # 0 status code is implied, but we will be explicit return 0 ) _swap "$@" __swap_rc=$? unset -f _swap if [ -n "${BASH_SOURCE[0]}" ] && [ "${BASH_SOURCE[0]}" != "$0" ]; then eval "unset __swap_rc; return $__swap_rc" fi eval "unset __swap_rc; exit $__swap_rc"