Motivation / Background
July 20, 2026 · View on GitHub
Atomic mv to place output is a common idiom. Same dir/FS is not always natural.
To me this means OS mv's SHOULD have grown --atomic DECADES ago. While my
motivating use-case was the rather ornate example, it is far from unique.
Opt-in vs. opt-out is debatable, but it has always been the case that ENOSPC
may be preferred to EXDEV. Either is catchable by outer logic.
Usage
mova SRC DST | mova -h
Like mv (1), try rename (2) first - already atomic on same FS mount. On
EXDEV, mova falls back to copy to a tmp file in DST's dir + atomic rename +
fsync + unlink SRC where ENOSPC is a possible failure. Preserves SRC
owner, perm bits & [am]time (usually all that's needed for placement idioms).
Example: Edit a file w/\0 rather than \n row termination
This assumes GNU sed & no actual "↲" in rows. (A native \0-capable $EDITOR can do without the no-special-string-in rows requirement):
e0() { # Edit 0-term # vim: set ft=zsh sw=2:
# Needs bu/mova for cross-FS-place safety, but maybe YOUR \$1 & /tmp share mount?
type mova >/dev/null >&2 && mv=mova || mv=mv
local p="${TMPDIR:-/tmp}/h${UID}_" # One per-UID name=~non-colliding enough
[[ -e "${p}0" || -e "${p}1" || -e "${p}2" || -e "${p}3" ]] && {
print -u2 "e0: stale ${p}[0-3]"; return 1 # Abort if files are left over..
} #..probably for manual clean-up.
cp "$e0" "${p}0"||return 1 # Warn mostly to train users => small races are ok
0n < "$e0" | tee "${p}1" > "${p}2" # Transform for edit & later cmp
${=EDITOR:-vi} "${p}2" # MAIN USER ACTION - MANUAL EDIT; Minimal wd-split
if cmp "${p}1" "${p}2" >/dev/null # Check if anything changed
then print -u2 "e0: did not alter $e0" # If not, do not cause a ruckus.
else n0 < "${p}2" > "${p}3" && { # Transform back
cmp "${p}0" "$e0" >/dev/null ||print -u2 "e0: Lost newly completed commands"
$mv "${p}3" "$e0"||print -u2 "e0: replace failed" } # Cross-mount atomic plc
fi
zf_rm -f "${p}"[0-3] # Clean-up
}
0n() { sed -z 's/\n/↲/g' | tr \\0 \\n } # \0-term -> \n-term for edit
n0() { tr \\n \\0 | sed -z 's/↲/\n/g' } # \n-term -> \0-term for posterity
For me, /tmp or $TMPDIR are typically a /dev/shm RAM filesystem. So, the
cross mount point/FS case is the common case.