Convert absolute path into relative path given a c

2019-01-01 09:54发布

Example:

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

# Magic

relative="../../bar"

How do I create the magic (hopefully not too complicated code...)?

23条回答
倾城一夜雪
2楼-- · 2019-01-01 10:29

My Solution:

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

}
查看更多
浅入江南
3楼-- · 2019-01-01 10:30

Presuming that you have installed: bash, pwd, dirname, echo; then relpath is

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

I've golfed the answer from pini and a few other ideas

查看更多
不流泪的眼
4楼-- · 2019-01-01 10:30

Not a lot of the answers here are practical for every day use. Since it is very difficult to do this properly in pure bash, I suggest the following, reliable solution (similar to one suggestion buried in a comment):

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

Then, you can get the relative path based upon the current directory:

echo $(relpath somepath)

or you can specify that the path be relative to a given directory:

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

The one disadvantage is this requires python, but:

  • It works identically in any python >= 2.6
  • It does not require that the files or directories exist.
  • Filenames may contain a wider range of special characters. For example, many other solutions do not work if filenames contain spaces or other special characters.
  • It is a one-line function that doesn't clutter scripts.

Note that solutions which include basename or dirname may not necessarily be better, as they require that coreutils be installed. If somebody has a pure bash solution that is reliable and simple (rather than a convoluted curiosity), I'd be surprised.

查看更多
回忆,回不去的记忆
5楼-- · 2019-01-01 10:35

It is built in to Perl since 2001, so it works on nearly every system you can imagine, even VMS.

perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' FILE BASE

Also, the solution is easy to understand.

So for your example:

perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $absolute $current

...would work fine.

查看更多
公子世无双
6楼-- · 2019-01-01 10:38

This script gives correct results only for inputs that are absolute paths or relative paths without . or ..:

#!/bin/bash

# usage: relpath from to

if [[ "$1" == "$2" ]]
then
    echo "."
    exit
fi

IFS="/"

current=($1)
absolute=($2)

abssize=${#absolute[@]}
cursize=${#current[@]}

while [[ ${absolute[level]} == ${current[level]} ]]
do
    (( level++ ))
    if (( level > abssize || level > cursize ))
    then
        break
    fi
done

for ((i = level; i < cursize; i++))
do
    if ((i > level))
    then
        newpath=$newpath"/"
    fi
    newpath=$newpath".."
done

for ((i = level; i < abssize; i++))
do
    if [[ -n $newpath ]]
    then
        newpath=$newpath"/"
    fi
    newpath=$newpath${absolute[i]}
done

echo "$newpath"
查看更多
梦该遗忘
7楼-- · 2019-01-01 10:39

This is a corrected, fully functional improvement of the currently best rated solution from @pini (which sadly handle only a few cases)

Reminder : '-z' test if the string is zero-length (=empty) and '-n' test if the string is not empty.

# both $1 and $2 are absolute paths beginning with /
# returns relative path to $2/$target from $1/$source
source=$1
target=$2

common_part=$source # for now
result="" # for now

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:1}"
fi

echo $result

Test cases :

compute_relative.sh "/A/B/C" "/A"           -->  "../.."
compute_relative.sh "/A/B/C" "/A/B"         -->  ".."
compute_relative.sh "/A/B/C" "/A/B/C"       -->  ""
compute_relative.sh "/A/B/C" "/A/B/C/D"     -->  "D"
compute_relative.sh "/A/B/C" "/A/B/C/D/E"   -->  "D/E"
compute_relative.sh "/A/B/C" "/A/B/D"       -->  "../D"
compute_relative.sh "/A/B/C" "/A/B/D/E"     -->  "../D/E"
compute_relative.sh "/A/B/C" "/A/D"         -->  "../../D"
compute_relative.sh "/A/B/C" "/A/D/E"       -->  "../../D/E"
compute_relative.sh "/A/B/C" "/D/E/F"       -->  "../../../D/E/F"
查看更多
登录 后发表回答