例子:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
我如何创造魔法(希望不是太复杂的代码…)?
例子:
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解决方案(而不是令人费解的好奇心),我会感到惊讶。
其他回答
#!/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/}
下面是一个shell脚本,它可以在不调用其他程序的情况下完成:
#! /bin/env bash
#bash script to find the relative path between two directories
mydir=${0%/}
mydir=${0%/*}
creadlink="$mydir/creadlink"
shopt -s extglob
relpath_ () {
path1=$("$creadlink" "$1")
path2=$("$creadlink" "$2")
orig1=$path1
path1=${path1%/}/
path2=${path2%/}/
while :; do
if test ! "$path1"; then
break
fi
part1=${path2#$path1}
if test "${part1#/}" = "$part1"; then
path1=${path1%/*}
continue
fi
if test "${path2#$path1}" = "$path2"; then
path1=${path1%/*}
continue
fi
break
done
part1=$path1
path1=${orig1#$part1}
depth=${path1//+([^\/])/..}
path1=${path2#$path1}
path1=${depth}${path2#$part1}
path1=${path1##+(\/)}
path1=${path1%/}
if test ! "$path1"; then
path1=.
fi
printf "$path1"
}
relpath_test () {
res=$(relpath_ /path1/to/dir1 /path1/to/dir2 )
expected='../dir2'
test_results "$res" "$expected"
res=$(relpath_ / /path1/to/dir2 )
expected='path1/to/dir2'
test_results "$res" "$expected"
res=$(relpath_ /path1/to/dir2 / )
expected='../../..'
test_results "$res" "$expected"
res=$(relpath_ / / )
expected='.'
test_results "$res" "$expected"
res=$(relpath_ /path/to/dir2/dir3 /path/to/dir1/dir4/dir4a )
expected='../../dir1/dir4/dir4a'
test_results "$res" "$expected"
res=$(relpath_ /path/to/dir1/dir4/dir4a /path/to/dir2/dir3 )
expected='../../../dir2/dir3'
test_results "$res" "$expected"
#res=$(relpath_ . /path/to/dir2/dir3 )
#expected='../../../dir2/dir3'
#test_results "$res" "$expected"
}
test_results () {
if test ! "$1" = "$2"; then
printf 'failed!\nresult:\nX%sX\nexpected:\nX%sX\n\n' "$@"
fi
}
#relpath_test
来源:http://www.ynform.org/w/Pub/Relpath
这里的答案并不是每天都能用的。由于在纯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
我的解决方案:
computeRelativePath()
{
Source=$(readlink -f ${1})
Target=$(readlink -f ${2})
local OLDIFS=$IFS
IFS="/"
local SourceDirectoryArray=($Source)
local TargetDirectoryArray=($Target)
local SourceArrayLength=$(echo ${SourceDirectoryArray[@]} | wc -w)
local TargetArrayLength=$(echo ${TargetDirectoryArray[@]} | wc -w)
local Length
test $SourceArrayLength -gt $TargetArrayLength && Length=$SourceArrayLength || Length=$TargetArrayLength
local Result=""
local AppendToEnd=""
IFS=$OLDIFS
local i
for ((i = 0; i <= $Length + 1 ; i++ ))
do
if [ "${SourceDirectoryArray[$i]}" = "${TargetDirectoryArray[$i]}" ]
then
continue
elif [ "${SourceDirectoryArray[$i]}" != "" ] && [ "${TargetDirectoryArray[$i]}" != "" ]
then
AppendToEnd="${AppendToEnd}${TargetDirectoryArray[${i}]}/"
Result="${Result}../"
elif [ "${SourceDirectoryArray[$i]}" = "" ]
then
Result="${Result}${TargetDirectoryArray[${i}]}/"
else
Result="${Result}../"
fi
done
Result="${Result}${AppendToEnd}"
echo $Result
}