获取两条路径之间的相对链接

Ame*_*ina 31 shell zsh symlink files

假设我有两条路径:<source_path><target_path>。我希望我的 shell (zsh) 自动找出是否有办法将<target_path>from表示<source_path>为相对路径。

例如让我们假设

  • <source_path>/foo/bar/something
  • <target_path>/foo/hello/world

结果是 ../../hello/world

为什么我需要这个:

我需要<source_path>尽可能<target_path>使用相对符号链接创建一个符号链接,否则当我从 Windows 访问网络上的这些文件时,我们的 samba 服务器无法正确显示文件(我不是系统管理员,并且不无法控制此设置)

假设<target_path><source_path>是绝对路径,下面创建一个指向绝对路径的符号链接。

ln -s <target_path> <source_path>
Run Code Online (Sandbox Code Playgroud)

所以它不适合我的需要。我需要为数百个文件执行此操作,因此我无法手动修复它。

任何 shell 内置程序可以解决这个问题?

ken*_*orb 37

尝试使用realpath命令(GNU 的一部分coreutils>=8.23),例如:

realpath --relative-to=/foo/bar/something /foo/hello/world
Run Code Online (Sandbox Code Playgroud)

如果您使用的是 macOS,请通过以下方式安装 GNU 版本:brew install coreutils并使用grealpath.

请注意,要使命令成功,两条路径都需要存在。如果您仍然需要相对路径,即使其中之一不存在,请添加 -m 开关。

有关更多示例,请参阅将绝对路径转换为给定当前目录的相对路径


Sté*_*las 11

您可以使用该symlinks命令将绝对路径转换为相对路径:

/tmp$ mkdir -p 1/{a,b,c} 2
/tmp$ cd 2
/tmp/2$ ln -s /tmp/1/* .
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 a -> /tmp/1/a/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 b -> /tmp/1/b/
lrwxrwxrwx 1 stephane stephane 8 Jul 31 16:32 c -> /tmp/1/c/
Run Code Online (Sandbox Code Playgroud)

我们有绝对链接,让我们将它们转换为相对链接:

/tmp/2$ symlinks -cr .
absolute: /tmp/2/a -> /tmp/1/a
changed:  /tmp/2/a -> ../1/a
absolute: /tmp/2/b -> /tmp/1/b
changed:  /tmp/2/b -> ../1/b
absolute: /tmp/2/c -> /tmp/1/c
changed:  /tmp/2/c -> ../1/c
/tmp/2$ ls -l
total 0
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 a -> ../1/a/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 b -> ../1/b/
lrwxrwxrwx 1 stephane stephane 6 Jul 31 16:32 c -> ../1/c/
Run Code Online (Sandbox Code Playgroud)

参考


ter*_*don 8

我认为这个 python 解决方案(取自这个 SO answer)也值得一提。将此添加到您的~/.zshrc

relpath() python -c 'import os.path, sys;\
  print os.path.relpath(sys.argv[1],sys.argv[2])' "$1" "${2-$PWD}"
Run Code Online (Sandbox Code Playgroud)

然后你可以做,例如:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs
Run Code Online (Sandbox Code Playgroud)


小智 7

没有任何 shell 内置函数来处理这个问题。不过,我在 Stack Overflow 上找到了解决方案。我已将解决方案复制到此处并稍作修改,并将此答案标记为社区维基。

relpath() {
    # both $1 and $2 are absolute paths beginning with /
    # $1 must be a canonical path; that is none of its directory
    # components may be ".", ".." or a symbolic link
    #
    # returns relative path to $2/$target from $1/$source
    source=$1
    target=$2

    common_part=$source
    result=

    while [ "${target#"$common_part"}" = "$target" ]; do
        # no match, means that candidate common part is not correct
        # go up one level (reduce common part)
        common_part=$(dirname "$common_part")
        # and record that we went back, with correct / handling
        if [ -z "$result" ]; then
            result=..
        else
            result=../$result
        fi
    done

    if [ "$common_part" = / ]; then
        # special case for root (no common path)
        result=$result/
    fi

    # since we now have identified the common part,
    # compute the non-common part
    forward_part=${target#"$common_part"}

    # and now stick all parts together
    if [ -n "$result" ] && [ -n "$forward_part" ]; then
        result=$result$forward_part
    elif [ -n "$forward_part" ]; then
        # extra slash removal
        result=${forward_part#?}
    fi

    printf '%s\n' "$result"
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用这个函数:

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"
Run Code Online (Sandbox Code Playgroud)