将绝对符号链接转换为相对符号链接

问题描述 投票:0回答:7

我 rsync 目录“Promotion”,其中包含具有不同目录结构的两台计算机之间的绝对符号链接。因此绝对符号链接在两台机器上都不起作用。为了使它们工作,我想将它们转换为相对链接。 目录结构是

Machine 1: /home/user/Privat/Uni Kram/Promotion/
Machine 2: /homes/user/Promotion/

以下是两个示例符号链接:

 4821      1 lrwxrwxrwx   1 manu  users         105 Nov 17  2014 ./Planung\ nach\ Priorit\303\244ten.ods -> /home/manu/Dokumente/Privat/Uni\ Kram/Promotion/Pl\303\244ne\ und\ Ideen/Pl\303\244ne/Planung\ nach\ Priorit\303\244ten.ods  
37675      1 lrwxrwxrwx   1 manu  users         102 Aug  3  2015 ./Kurs/Lab\ Course\ Somewhere -> /home/manu/Dokumente/Privat/Uni\ Kram/Promotion/Workshops\ &\ Fortbildungen/Kurs\ Lab\ Course\ Somewhere

我的非工作尝试是(基于示例this):

find * -type l -print | while read l; do 
ln -srf $(cut -c 24- < $(readlink $l)) $l;
done
bash sh symlink
7个回答
6
投票

在包含正确符号链接文件的计算机上,运行以下命令:

find . -type l -exec ln -sfr {} . \;

要仅更改当前目录中的符号链接而不遍历所有子目录,请使用

-maxdepth 1
标志,因为上述命令会将子目录中的符号链接移动到当前目录中。

此外,macos 提供的本机 BSD

ln
命令没有
-r
标志,但可以使用 GNU core utils 提供的
ln
命令来代替,如果通过自制程序安装,则将
g
前置到
ln
IE。
gln -sfr


1
投票

这是使用 python3 执行此操作的解决方案。

from pathlib import Path

d = Path("/home/user/Privat/Uni Kram/Promotion/")
all_symlinks = [p for p in d.rglob("*") if p.is_symlink()]

def make_symlink_relative(p):
    assert p.is_symlink()
    relative_target = p.resolve(strict=True).relative_to(p.absolute().parent)
    p.unlink() 
    # this while-loop protects against race conditions
    while True:
        try:
            p.symlink_to(relative_target)
            break
        except FileExistsError:
            pass

for symlink in all_symlinks:
    make_symlink_relative(symlink)


0
投票

以下可能有效。

请注意

  • 链接已备份,如果不需要更改
    mv ..
    通过
    rm
  • find 参数必须是绝对的,否则函数将拒绝链接

示例

find /absolute/path/to/root -type l -exec bash -c $'
    abs_to_relative() {
        l=$1
        t=$(readlink "$1")
        [[ $l = /* ]] && [[ $t = /* ]] && [[ $l != "$t" ]] || return
        set -f
        IFS=/
        link=($l)
        target=($t)
        IFS=$\' \\t\\n\'
        set +f
        i=0 f=\'\' res=\'\'
        while [[ ${link[i]} = "${target[i]}" ]]; do ((i+=1)); done
        link=("${link[@]:i}")
        target=("${target[@]:i}")
        for ((i=${#link[@]};i>1;i-=1)); do res+=../; done
        res=${res:-./}
        for f in "${target[@]}"; do res+=$f/; done
        res=${res%/}
        # rm "$1"
        mv "$1" "$1.bak"
        ln -s "$res" "$1"
    }
    for link; do abs_to_relative "$link"; done
' links {} +

测试已完成

mkdir -p /tmp/testlink/home{,s}/user/abc
touch /tmp/testlink/home/user/{file0,abc/file1}.txt
ln -s /tmp/testlink/home/user/abc/file1.txt /tmp/testlink/home/user/link1.txt
ln -s /tmp/testlink/home/user/file0.txt /tmp/testlink/home/user/abc/link0.txt
ln -s /tmp/testlink/home/user/    /tmp/testlink/home/user/abc/linkdir
# ... command
rm -rf /tmp/testlink

0
投票

谢谢大家的帮助。经过一番尝试后,我根据您的评论和代码提出了一个解决方案。 这就是解决我的问题的方法:

#!/bin/bash
# changes all symbolic links to relative ones recursive from the current directory
find * -type l -print | while read l; do 
cp -a "$l" "$l".bak
linkname="$l"
linktarget=$(readlink "$l")
echo "orig linktarget"
echo $linktarget
temp_var="${linktarget#/home/user/Privat/Uni Kram/Promotion/}"
echo "changed linktarget"
echo $temp_var;
ln -sfr "$temp_var" "$l"
echo "new linktarget in symlink"
readlink "$l";
done

0
投票

这里有一对用于双向转换的函数。我现在将这些放在我的

~/bin
中以便更广泛地使用。这些链接不必位于当前目录中。只需用作:

lnsrelative <link> <link> <link> ...
lnsabsolute <link> <link> <link> ...
#!/bin/bash
# changes absolute symbolic links to relative
# will work for links & filenames with spaces

for l in "$@"; do
    [[ ! -L "$l" ]] && echo "Not a link: $l" && exit 1
done
    
for l in "$@"; do
    # Use -b here to get a backup.  Unnecessary because reversible.
    ln -sfr "$(readlink "$l")" "$l"
done

#!/bin/bash
# changes relative symbolic links to absolute
# will work for links & filenames with spaces

for l in "$@"; do
    [[ ! -L "$l" ]] && echo "Not a link: $l" && exit 1
done
    
for l in "$@"; do
    # Use -b here to get a backup.  Unnecessary because reversible.
    ln -sf "$(realpath "$(readlink "$l")")" "$l"
done

为了完整起见,这会将符号链接更改为硬链接,用作:

lnstohard <link> <link> <link>
#!/bin/bash
# changes symbolic links to hard links
# This will work for filenames with spaces, 
# but only for regular files in the same filesystem as the link.
# Thorough discussion of correct way to confirm devices are the same:
# https://unix.stackexchange.com/questions/120810/check-if-2-directories-are-hosted-on-the-same-partition-on-linux

for l in "$@"; do
    [[ ! -L "$l" ]] && echo "Not a symbolic link: $l" && exit 1
    
    rl="$(readlink "$l")"
    rld="$(dirname "$l")"
    [[ ! -e "$rl" || -d "$rl" || "$(df --output=target "$rld")" != "$(df --output=target "$rl")" ]] && \
        echo "Target \"$rl\" must exist on same filesystem as link \"$l\", and may not be a directory" && \
        exit 1
done

for l in "$@"; do
    # Using -b here to get a backup, because it's not easy to revers a soft->hard link conversion
    ln -fb "$(readlink "$l")" "$l"
done

0
投票

虽然@Diagon的答案更完整,但这里有一个快速而肮脏的单行代码,它在大多数情况下都有效,但可能会死在带有空格的路径上:

find . -type l -exec bash -c  'ln -sfr {} $(dirname {})/' \;

0
投票

这是一个答案,可以处理符号链接名称和符号链接目标中嵌入换行符和其他特殊字符的文件名。这需要 GNU

find
ln
:

find . -type l -exec printf "ln --no-target-directory -srf -- %q %q\n" {} {} \; | bash

删除末尾的

| bash
以查看其功能的预览。

基本上,它列出了每个符号链接,并将名称两次传递给带有标志

ln
--no-target-directory
,以允许原始符号链接同时作为源和目标。然后,GNU
-r
ln
标志将自动将符号链接目标重写为相对符号链接,无论原始符号链接有多复杂。
printf %q
用于对符号链接名称中的所有特殊字符进行编码。

你可以给其他参数,但

.
find
从其他目录向下搜索。

如果您只需要重写一个符号链接,您可以这样做:

LINK="/path/to/symlink/I want to fix"
ln --no-target-directory -srf -- "$LINK" "$LINK"

这也将正确处理符号链接名称与目标名称不匹配的情况(例如

a -> ../b
)。

© www.soinside.com 2019 - 2024. All rights reserved.