Эта программа принимает символическую ссылку в качестве аргумента и меняет местами ссылку на ее референт. Например, учитывая a -> b
, результат будет b -> a
. Он создает относительную ссылку, если оригинал был относительным, в противном случае — абсолютную ссылку.
Я хотел убедиться, что это команда «все или ничего», поэтому в случае сбоя она оставляет все как есть. Это не полностью достижимо в условиях одновременной модификации, но оно делает все возможное, чтобы отменить изменения, если они были неудачными. Я также использую rm
с --no-clobber
и ln
без --force
чтобы избежать потери данных в любом случае.
#!/bin/bash
set -eu -o pipefail
die()
{
printf '%q: ' "$1"; shift
printf '%sn' "$@"
exit 1
}
usage()
{
cat <<END
Usage: $0 FILE
FILE a symlink
The symlink will be swapped with its target
END
}
undo=""
add_undo()
{
# prepend a command to the undo list
local command
printf -v command '%q ' "$@"
undo="$command"$'n'"$undo"
}
restore()
{
# execute the undo commands
eval "$undo" ||
die "$0" "Failed to restore to initial state!"
}
trap restore ERR;
if [ $# -ne 1 ]
then
usage >&2
exit 1
fi
case "$1" in
-h|--help)
usage; exit ;;
esac
test -L "$1" || die "$1" 'not a symlink'
test -e "$1" || die "$1" 'dangling symlink'
target=$(readlink -e "$1")
lnopts=(--symbolic)
case "$(readlink "$1")" in
/*)
# make $1 absolute, without following the link itself
set -- "$(realpath --no-symlinks "$1")"
;;
*)
lnopts+=(--relative)
;;
esac
# Create temporary working directory alongside the link
linkdir="$(mktemp --directory --tmpdir="$(dirname "$1")")"
test -d "$linkdir"
add_undo rm -r "$linkdir"
# Move the symlink into tempdir
mv -T "$1" "$linkdir/link"
add_undo mv -T "$linkdir/link" "$1"
# Create a new symlink next to the target
newlink="$(mktemp --dry-run --tmpdir="$(dirname "$target")")"
test -n "$newlink"
ln "${lnopts[@]}" "$1" "$newlink"
add_undo rm "$newlink"
# Move the target to its new location
# This is the riskiest and most expensive thing to restore, so do it last
mv -T --no-clobber "$target" "$1"
add_undo mv "$1" "$target"
# Move the new link into position left by target
mv -T --no-clobber "$newlink" "$target"
# Succeeded, so remove the temporary directory
undo=""
rm -r "$linkdir"