-
-
Save cmcaine/1b5fab9e9e958877166742f42d5b831d to your computer and use it in GitHub Desktop.
Swap two files reasonably safely
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Swap two files safely. | |
# | |
# Under normal circumstances, just do the right thing. Under unusual | |
# circumstances, give a reasonably helpful error message. | |
# | |
# This was an exercise in thinking about how generally reliable functions might | |
# fail and what to communicate to users. | |
# | |
# Copyright Colin Caine. License: GPLv2 | |
USAGE="Usage: `basename $0` file1 file2 | |
Basically the same as: | |
mv file1 \$tmpfile | |
mv file2 file1 | |
mv \$tmpfile file1 | |
But does something sensible if errors occur. | |
" | |
use_error () { | |
[ -n "$1" ] && echo -e "$1\n" 2>&1 | |
echo "$USAGE" | |
exit 1 | |
} | |
descriptive_mv () { | |
# $1 and $2 are the paths to two files in the same directory. | |
# As of a moment ago, both files existed and the directory was writable by | |
# our user (we could make the $tmpfile). | |
if ! mv "$1" "$2"; then | |
# Something bananas has happened. mv won't necessarily say something | |
# sensible, so let's be as helpful as we can. | |
echo "Error: mv '$1' '$2' failed! | |
This is quite unusual. | |
Some possible reasons: | |
1) $USER's write access to dir `dirname $1` or execute permission on | |
parent directories has been changed during the runtime of this | |
script. | |
--> Data should be in '$1' | |
2) '$1' has been moved or deleted by something else | |
3) You're using a filesystem or kernel with odd properties (FUSE?) | |
--> Your data may be in '$1' or '$2'" >&2 | |
exit 100 | |
fi | |
} | |
file1="$1" | |
file2="$2" | |
if [ ! $# = 2 ]; then | |
use_error | |
elif [ ! -e "$file1" ]; then | |
use_error "Error: '$file1' does not exist!" | |
elif [ ! -e "$file2" ]; then | |
use_error "Error: '$file2' does not exist!" | |
else | |
if ! tmpfile=$(mktemp $(dirname "$file1")/"$file1".tmp.XXXXXXXXXXX); then | |
# The error message from mktemp or whatever should be enough. | |
echo "Error: Could not create temporary file!" >&2 | |
exit 2 | |
fi | |
descriptive_mv "$file1" "$tmpfile" | |
if ! mv "$file2" "$file1"; then | |
if [ ! -e "$file1" ]; then | |
# $USER probably doesn't have permission to delete or rename $file2. | |
# $file2 will be unmodified, so just put $file1 back where it was to | |
# rollback the swap. | |
descriptive_mv "$tmpfile" "$file1" | |
echo "Error: mv '$file2' '$file1' failed. | |
Swap rolled back: files unchanged." >&2 | |
exit 3 | |
else | |
# If $file1 exists, `mv $file2 $file1` failed in a highly unusual way, or | |
# $file1 was created again after we moved it the first time. | |
echo "Error: mv '$file2' '$file1' failed. | |
Tried to move '$tmpfile' back to '$file1', but '$1' already exists! | |
Possible reasons: | |
1) A new '$file1' was created after this script renamed the original | |
--> original '$file1' is in '$tmpfile' | |
'$file2' is where it started | |
2) mv failed in a highly unusual way (weird filesystem?) | |
--> original '$file1' is in '$tmpfile' | |
original '$file2' might be in '$file1' or '$file2'" >&2 | |
exit 5 | |
fi | |
fi | |
if ! mv "$tmpfile" "$file2"; then | |
# Maybe $file1 and $file2 were on different devices and $file1 (now | |
# $tmpfile) won't fit onto $file2's device. Maybe something else exciting. | |
echo "Error: Couldn't complete final move. | |
Succeeded: 'mv '$file1' '$tmpfile;' mv '$file2' '$file1;'' | |
Failed: 'mv '$tmpfile' '$file2'' | |
Content of the file originally called '$file1' is probably in '$tmpfile'. | |
In extraordinary circumstances, the content may be in '$file2'." >&2 | |
exit 4 | |
fi | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment