例子:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
我如何创造魔法(希望不是太复杂的代码…)?
例子:
absolute="/foo/bar"
current="/foo/baz/foo"
# Magic
relative="../../bar"
我如何创造魔法(希望不是太复杂的代码…)?
当前回答
我需要这样的东西,但它也解决了符号链接。我发现pwd有一个-P标志用于此目的。附加了我的脚本的一个片段。它在shell脚本的函数中,因此是$1和$2。结果值是从START_ABS到END_ABS的相对路径,位于UPDIRS变量中。为了执行pwd -P,将脚本cd放入每个参数目录,这也意味着将处理相对路径参数。干杯,吉姆
SAVE_DIR="$PWD"
cd "$1"
START_ABS=`pwd -P`
cd "$SAVE_DIR"
cd "$2"
END_ABS=`pwd -P`
START_WORK="$START_ABS"
UPDIRS=""
while test -n "${START_WORK}" -a "${END_ABS/#${START_WORK}}" '==' "$END_ABS";
do
START_WORK=`dirname "$START_WORK"`"/"
UPDIRS=${UPDIRS}"../"
done
UPDIRS="$UPDIRS${END_ABS/#${START_WORK}}"
cd "$SAVE_DIR"
其他回答
假设您已经安装了: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和其他一些想法中得到了答案
注意:这要求两个路径都是现有文件夹。文件将无法工作。
这个脚本只对路径名有效。它不需要任何文件存在。如果传递的路径不是绝对的,那么行为就有点不寻常,但是如果两条路径都是相对的,那么应该能正常工作。
我只在OS X上测试过,所以可能不太便携。
#!/bin/bash
set -e
declare SCRIPT_NAME="$(basename $0)"
function usage {
echo "Usage: $SCRIPT_NAME <base path> <target file>"
echo " Outputs <target file> relative to <base path>"
exit 1
}
if [ $# -lt 2 ]; then usage; fi
declare base=$1
declare target=$2
declare -a base_part=()
declare -a target_part=()
#Split path elements & canonicalize
OFS="$IFS"; IFS='/'
bpl=0;
for bp in $base; do
case "$bp" in
".");;
"..") let "bpl=$bpl-1" ;;
*) base_part[${bpl}]="$bp" ; let "bpl=$bpl+1";;
esac
done
tpl=0;
for tp in $target; do
case "$tp" in
".");;
"..") let "tpl=$tpl-1" ;;
*) target_part[${tpl}]="$tp" ; let "tpl=$tpl+1";;
esac
done
IFS="$OFS"
#Count common prefix
common=0
for (( i=0 ; i<$bpl ; i++ )); do
if [ "${base_part[$i]}" = "${target_part[$common]}" ] ; then
let "common=$common+1"
else
break
fi
done
#Compute number of directories up
let "updir=$bpl-$common" || updir=0 #if the expression is zero, 'let' fails
#trivial case (after canonical decomposition)
if [ $updir -eq 0 ]; then
echo .
exit
fi
#Print updirs
for (( i=0 ; i<$updir ; i++ )); do
echo -n ../
done
#Print remaining path
for (( i=$common ; i<$tpl ; i++ )); do
if [ $i -ne $common ]; then
echo -n "/"
fi
if [ "" != "${target_part[$i]}" ] ; then
echo -n "${target_part[$i]}"
fi
done
#One last newline
echo
这是我的版本。这是基于@Offirmo的回答。我使它与dash兼容,并修复了以下测试用例失败:
sh - compute-relative。“a / b / c - de - f / g " / a / b / c / def / g /" --> "../.. f - g - "
Now:
CT_FindRelativePath”a / b / c - de - f / g " / a / b / c / def / g /" --> "../../../ def - g”
查看代码:
# both $1 and $2 are absolute paths beginning with /
# returns relative path to $2/$target from $1/$source
CT_FindRelativePath()
{
local insource=$1
local intarget=$2
# Ensure both source and target end with /
# This simplifies the inner loop.
#echo "insource : \"$insource\""
#echo "intarget : \"$intarget\""
case "$insource" in
*/) ;;
*) source="$insource"/ ;;
esac
case "$intarget" in
*/) ;;
*) target="$intarget"/ ;;
esac
#echo "source : \"$source\""
#echo "target : \"$target\""
local common_part=$source # for now
local result=""
#echo "common_part is now : \"$common_part\""
#echo "result is now : \"$result\""
#echo "target#common_part : \"${target#$common_part}\""
while [ "${target#$common_part}" = "${target}" -a "${common_part}" != "//" ]; 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
if [ -z "${result}" ]; then
result="../"
else
result="../$result"
fi
#echo "(w) common_part is now : \"$common_part\""
#echo "(w) result is now : \"$result\""
#echo "(w) target#common_part : \"${target#$common_part}\""
done
#echo "(f) common_part is : \"$common_part\""
if [ "${common_part}" = "//" ]; then
# special case for root (no common path)
common_part="/"
fi
# since we now have identified the common part,
# compute the non-common part
forward_part="${target#$common_part}"
#echo "forward_part = \"$forward_part\""
if [ -n "${result}" -a -n "${forward_part}" ]; then
#echo "(simple concat)"
result="$result$forward_part"
elif [ -n "${forward_part}" ]; then
result="$forward_part"
fi
#echo "result = \"$result\""
# if a / was added to target and result ends in / then remove it now.
if [ "$intarget" != "$target" ]; then
case "$result" in
*/) result=$(echo "$result" | awk '{ string=substr($0, 1, length($0)-1); print string; }' ) ;;
esac
fi
echo $result
return 0
}
使用GNU coreutils 8.23中的realpath是最简单的,我认为:
$ realpath --relative-to="$file1" "$file2"
例如:
$ realpath --relative-to=/usr/bin/nmap /tmp/testing
../../../tmp/testing
我使用的macOS默认情况下没有realpath命令,所以我做了一个纯bash函数来计算它。
#!/bin/bash
##
# print a relative path from "source folder" to "target file"
#
# params:
# $1 - target file, can be a relative path or an absolute path.
# $2 - source folder, can be a relative path or an absolute path.
#
# test:
# $ mkdir -p ~/A/B/C/D; touch ~/A/B/C/D/testfile.txt; touch ~/A/B/testfile.txt
#
# $ getRelativePath ~/A/B/C/D/testfile.txt ~/A/B
# $ C/D/testfile.txt
#
# $ getRelativePath ~/A/B/testfile.txt ~/A/B/C
# $ ../testfile.txt
#
# $ getRelativePath ~/A/B/testfile.txt /
# $ home/bunnier/A/B/testfile.txt
#
function getRelativePath(){
local targetFilename=$(basename $1)
local targetFolder=$(cd $(dirname $1);pwd) # absolute target folder path
local currentFolder=$(cd $2;pwd) # absulute source folder
local result=.
while [ "$currentFolder" != "$targetFolder" ];do
if [[ "$targetFolder" =~ "$currentFolder"* ]];then
pointSegment=${targetFolder#$currentFolder}
result=$result/${pointSegment#/}
break
fi
result="$result"/..
currentFolder=$(dirname $currentFolder)
done
result=$result/$targetFilename
echo ${result#./}
}