Expanding on my comment to h3rrmiller's answer, and following the pattern of Gilles's snippet, here is another method of making a temporary copy, then moving it into place. I believe this method is not vulnerable to any race conditions. It does require the -n
flag on mv
, which is not part of the POSIX spec. However, it does seem to be widely available. (Verified on the GNU implementation, also BusyBox, FreeBSD, OS X.)
(
tmp=$(mktemp "${target}.XXXXXX") && # "mktemp -p" doesn't work on BSD
cp -- "$source" "$tmp" && # you may want to add -p flag
inode=$(ls -di "$tmp" | cut -d' ' -f1) && # see comment below
mv -n -- "$tmp" "$target" && # won't overwrite, but doesn't then exit non-0
if [ "$(ls -di "$target" | cut -d' ' -f1)" != "$inode" ]; then
rm -- "$tmp"; false
fi
)
In Linux, you can get the inode using stat -c'%i' "$tmp"
, but that's not portable.
Instead of verifying the inode, another idea would be to check whether a file still exists at path $tmp
. If the mv -n ...
succeeded then there shouldn't be. (Unless some other process has freshly created $tmp
before you checked to see whether it was missing.)
For efficiency, it'd make sense to add a check if $target
exists at the start of the routine. If we see that it does exist, we can fail immediately, rather than making the copy, trying to move it, then seeing that that failed so removing the copy. This is left as an (easy) exercise.