例子:

absolute="/foo/bar"
current="/foo/baz/foo"

# Magic

relative="../../bar"

我如何创造魔法(希望不是太复杂的代码…)?


当前回答

这里的答案并不是每天都能用的。由于在纯bash中很难正确地做到这一点,我建议以下可靠的解决方案(类似于注释中的一个建议):

function relpath() { 
  python -c "import os,sys;print(os.path.relpath(*(sys.argv[1:])))" "$@";
}

然后,你可以得到基于当前目录的相对路径:

echo $(relpath somepath)

或者你可以指定路径相对于给定的目录:

echo $(relpath somepath /etc)  # relative to /etc

一个缺点是这需要python,但是:

它在任何python >= 2.6中工作相同 它不要求文件或目录存在。 文件名可以包含更广泛的特殊字符。 例如,如果文件名包含 空格或其他特殊字符。 它是一个单行函数,不会使脚本混乱。

注意,包含basename或dirname的解决方案不一定更好,因为它们要求安装coreutils。如果有人有可靠而简单的纯bash解决方案(而不是令人费解的好奇心),我会感到惊讶。

其他回答

$ python -c "import os.path; print os.path.relpath('/foo/bar', '/foo/baz/foo')"

给:

../../bar
#!/bin/bash
# both $1 and $2 are absolute paths
# returns $2 relative to $1

source=$1
target=$2

common_part=$source
back=
while [ "${target#$common_part}" = "${target}" ]; do
  common_part=$(dirname $common_part)
  back="../${back}"
done

echo ${back}${target#$common_part/}

这个答案并没有解决问题的Bash部分,但是因为我试图使用这个问题中的答案在Emacs中实现这个功能,所以我就把它扔到那里。

Emacs实际上有一个开箱即用的函数:

ELISP> (file-relative-name "/a/b/c" "/a/b/c")
"."
ELISP> (file-relative-name "/a/b/c" "/a/b")
"c"
ELISP> (file-relative-name "/a/b/c" "/c/b")
"../../a/b/c"

假设您已经安装了:bash、pwd、dirname、echo;relpath是

#!/bin/bash
s=$(cd ${1%%/};pwd); d=$(cd $2;pwd); b=; while [ "${d#$s/}" == "${d}" ]
do s=$(dirname $s);b="../${b}"; done; echo ${b}${d#$s/}

我从pini和其他一些想法中得到了答案

注意:这要求两个路径都是现有文件夹。文件将无法工作。

test.sh:

#!/bin/bash                                                                 

cd /home/ubuntu
touch blah
TEST=/home/ubuntu/.//blah
echo TEST=$TEST
TMP=$(readlink -e "$TEST")
echo TMP=$TMP
REL=${TMP#$(pwd)/}
echo REL=$REL

测试:

$ ./test.sh 
TEST=/home/ubuntu/.//blah
TMP=/home/ubuntu/blah
REL=blah