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


21

假设我有两条路径:<source_path><target_path>。我想我的shell(zsh中)来自动找出是否有代表的方式<target_path>,从<source_path>相对路径。

例如,假设

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

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

为什么我需要这个:

我需要尽可能地创建从<source_path><target_path>使用相对符号链接的符号链接,因为否则当我从Windows访问网络上的这些文件时,我们的samba服务器将无法正确显示该文件(我不是sys admin,所以不要无法控制此设置)

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

ln -s <target_path> <source_path>

因此它无法满足我的需求。我需要对数百个文件执行此操作,因此不能仅仅手动对其进行修复。

有内置的shell可以解决这个问题吗?


您可以symlinks用来将绝对链接转换为相对链接。
斯特凡Chazelas

@StephaneChazelas怎么样?您能发表解释的答案吗?
terdon

Answers:


10

您可以使用以下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/

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

/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/

参考文献


谢谢斯蒂芬。这解决了根本问题,所以我接受了。就是说,拥有某种东西(例如zsh函数)采用两条路径(源和目标)并输出从源到目标的相对路径(此处没有符号链接),将是很棒的。那也将解决标题中提出的问题。
Amelio Vazquez-Reina

@ user815423426,terdon 已经回答了部分
斯特凡Chazelas

24

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

realpath --relative-to=/foo/bar/something /foo/hello/world

如果您使用的是macOS,请通过以下命令安装GNU版本,brew install coreutils然后使用grealpath

请注意,两个路径都必须存在才能使命令成功。如果仍然需要相对路径,即使其中之一不存在,也可以添加-m开关。

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


我得到了realpath: unrecognized option '--relative-to=.':( realpath版本1.19
phuclv,

@LưuVĩnhPhúc您需要使用GNU / Linux版本的realpath。如果您使用的是macOS,请通过进行安装brew install coreutils
kenorb

不,我使用Ubuntu 14.04 LTS
phuclv

@LưuVĩnhPhúc我realpath (GNU coreutils) 8.26在参数存在的地方。请参阅:gnu.org/software/coreutils/realpath再次检查您是否未使用别名(例如,以方式运行\realpath),以及如何从中安装版本coreutils
kenorb


7

我认为这个python解决方案(与@EvanTeitelman的答案来自同一帖子)也值得一提。将此添加到您的~/.zshrc

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

然后,您可以执行以下操作:

$ relpath /usr/local/share/doc/emacs /usr/local/share/fonts
../doc/emacs

@StephaneChazelas,这是链接答案中的直接复制粘贴。我是一个Perl家伙,基本上不了解python,所以我不知道如何使用sys.argv。如果发布的代码有误或可以改进,请多谢。
terdon

@StephaneChazelas我现在明白了,谢谢您的编辑。
terdon

7

没有任何内置的shell可以解决这个问题。我在堆栈溢出上找到了解决方案。我在此处复制了解决方案并进行了一些修改,并将此答案标记为社区Wiki。

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"
}

您可以像这样使用此功能:

source=/foo/bar/something
target=/foo/hello/world
ln -s "$(relpath "$source" "$target")" "$source"

3
# Prints out the relative path between to absolute paths. Trivial.
#
# Parameters:
# $1 = first path
# $2 = second path
#
# Output: the relative path between 1st and 2nd paths
relpath() {
    local pos="${1%%/}" ref="${2%%/}" down=''

    while :; do
        test "$pos" = '/' && break
        case "$ref" in $pos/*) break;; esac
        down="../$down"
        pos=${pos%/*}
    done

    echo "$down${ref##$pos/}"
}

这是解决问题的最简单,最漂亮的方法。

它也应该适用于所有类似bourne的shell。

这里的智慧是:完全不需要外部实用程序甚至是复杂的条件。要在类似bourne的shell上完成几乎所有的工作,只需几个前缀/后缀替换和一个while循环。

也可以通过PasteBin获得:http ://pastebin.com/CtTfvime


感谢您的解决方案。我发现要使其工作在A中,Makefile我需要在行中添加一对双引号pos=${pos%/*}(当然,在每行的末尾添加分号和反斜杠)。
Circonflexe

想法是不错,但是,在目前的很多情况下版本不起作用:relpath /tmp /tmprelpath /tmp /tmp/arelpath /tmp/a /。在加,你忘了提,所有的路径都必须是绝对的,规范化的(即/tmp/a/..不工作)
杰罗姆Pouiller

0

我不得不写一个 bash脚本才能可靠地执行此操作(当然也无需验证文件是否存在)。

用法

relpath <symlink path> <source path>

返回相对源路径。

例:

#/a/b/c/d/e   ->  /a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath /a/b/c/d/e   /a/b/CC/DD/EE

#./a/b/c/d/e   ->  ./a/b/CC/DD/EE       ->  ../../CC/DD/EE
relpath ./a/b/c/d/e  ./a/b/CC/DD/EE

都得到 ../../CC/DD/EE


要进行快速测试,您可以运行

curl -sL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- --test

结果:测试清单

/a             ->  /                 ->  .
/a/b           ->  /                 ->  ..
/a/b           ->  /a                ->  .
/a/b/c         ->  /a                ->  ..
/a/b/c         ->  /a/b              ->  .
/a/b/c         ->  /a/b/CC           ->  CC
/a/b/c/d/e     ->  /a                ->  ../../..
/a/b/c/d/e     ->  /a/b              ->  ../..
/a/b/c/d/e     ->  /a/b/CC           ->  ../../CC
/a/b/c/d/e     ->  /a/b/CC/DD/EE     ->  ../../CC/DD/EE
./a            ->  ./                ->  .
./a/b          ->  ./                ->  ..
./a/b          ->  ./a               ->  .
./a/b/c        ->  ./a               ->  ..
./a/b/c        ->  ./a/b             ->  .
./a/b/c        ->  ./a/b/CC          ->  CC
./a/b/c/d/e    ->  ./a               ->  ../../..
./a/b/c/d/e    ->  ./a/b/CC          ->  ../../CC
./a/b/c/d/e    ->  ./a/b/CC/DD/EE    ->  ../../CC/DD/EE
/a             ->  /x                ->  x
/a/b           ->  /x                ->  ../x
/a/b/c         ->  /x                ->  ../../x
/a             ->  /x/y              ->  x/y
/a/b           ->  /x/y              ->  ../x/y
/a/b/c         ->  /x/y              ->  ../../x/y
/x             ->  /a                ->  a
/x             ->  /a/b              ->  a/b
/x             ->  /a/b/c            ->  a/b/c
/x/y           ->  /a                ->  ../a
/x/y           ->  /a/b              ->  ../a/b
/x/y           ->  /a/b/c            ->  ../a/b/c
./a a/b/c/d/e  ->  ./a a/b/CC/DD/EE  ->  ../../CC/DD/EE
/x x/y y       ->  /a a/b b/c c      ->  ../a a/b b/c c

顺便说一句,如果您想使用此脚本而不保存它,并且您信任此脚本,那么您可以通过两种方式使用bash技巧来实现此目的。

relpath通过以下命令作为bash函数导入。(需要bash 4+)

source <(curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath)

或像curl bash组合一样即时调用脚本。

curl -sSL https://github.com/jjqq2013/bash-scripts/raw/master/common/relpath | bash -s -- /a/b/c/d/e /a/b/CC/DD/EE
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.