Поменять местами символическую ссылку на ее цель

Эта программа принимает символическую ссылку в качестве аргумента и меняет местами ссылку на ее референт. Например, учитывая 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"

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *