BASH: Convert absolute path into relative path given a current directory?

! /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/}.

Wonderful script -- short and clean. I applied an edit (Waiting on peer review): common_part=$source/ common_part=$(dirname $common_part)/ echo ${back}${target#$common_part} Existing script would fail due to inappropriate match on start of directory name when comparing, for example: "/foo/bar/baz" to "/foo/barsucks/bonk". Moving the slash into the var and out of the final eval corrects that bug.

– jcwenger Jun 10 at 15:31 This script simply doesn't work. Fails one a simple "one directory down" tests. The edits by jcwenger work a little better but tend to add an extra "../".

– Dr. Person Person II Jul 23 at 13:01 After spending 30 minutes trying to port the script below (by Dennis Williamson) to zsh, this script does the job perfectly for my simple use case, and it also works in zsh without modification. – nathan. F77 Oct 14 at 4:54.

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 ${absolutelevel} == ${currentlevel} do (( level++ )) if (( level > abssize || level > cursize )) then break fi done for ((i = level; I level)) then newpath=$newpath"/" fi newpath=$newpath".." done for ((i = level; I.

This appears to work. If the directories actually exist, use of $(readlink -f $1) and $(readlink -f $2) on the inputs can fix the problem where ". " or ".." appears in the inputs.

This can cause some trouble if the directories don't actually exist. – Dr. Person Person II Jul 23 at 13:30.

Sadly, Mark Rushakoff's answer (now deleted - it referenced the code from here) does not seem to work correctly when adapted to: source=/home/part2/part3/part4 target=/work/proj1/proj2 The thinking outlined in the commentary can be refined to make it work correctly for most cases. I'm about to assume that the script takes a source argument (where you are) and a target argument (where you want to get to), and that either both are absolute pathnames or both are relative. If one is absolute and the other relative, the easiest thing is to prefix the relative name with the current working directory - but the code below does not do that.

Beware The code below is close to working correctly, but is not quite right. There is the problem addressed in the comments from Dennis Williamson. There is also a problem that this purely textual processing of pathnames and you can be seriously messed up by weird symlinks.

The code does not handle stray 'dots' in paths like 'xyz/. /pqr'. The code does not handle stray 'double dots' in paths like 'xyz/../pqr'.

Trivially: the code does not remove leading '. /' from paths. Dennis's code is better because it fixes 1 and 5 - but has the same issues 2, 3, 4.

Use Dennis's code (and up-vote it ahead of this) because of that. (NB: POSIX provides a system call realpath() that resolves pathnames so that there are no symlinks left in them. Applying that to the input names, and then using Dennis's code would give the correct answer each time.It is trivial to write the C code that wraps realpath() - I've done it - but I don't know of a standard utility that does so.

) For this, I find Perl easier to use than shell, though bash has decent support for arrays and could probably do this too - exercise for the reader. So, given two compatible names, split them each into components: Set the relative path to empty. While the components are the same, skip to the next.

When corresponding components are different or there are no more components for one path: If there are no remaining source components and the relative path is empty, add ". " to the start. For each remaining source component, prefix the relative path with "../".

If there are no remaining target components and the relative path is empty, add ". " to the start. For each remaining target component, add the component to the end of the path after a slash.

Thus: #! /bin/perl -w use strict; # Should fettle the arguments if one is absolute and one relative: # Oops - missing functionality! # Split!

My(@source) = split '/', $ARGV0; my(@target) = split '/', $ARGV1; my $count = scalar(@source); $count = scalar(@target) if (scalar(@target) = scalar(@source) && $relpath eq ""); for (my $s = $i; $s = scalar(@target) && $relpath eq ""); for (my $t = $i; $t $i); $relpath = "$relpath.."; } for (my $t = $i; $t.

Your /home/part1/part2 to / has one too many ../. Otherwise, my script matches your output except mine adds an unnecessary . At the end of the one where the destination is .

And I don't use a . / at the beginning of ones that descend without going up. – Dennis Williamson Apr 2 '10 at 5:40 @Dennis: I spent time going cross-eyed over the results - sometimes I could see that problem, and sometimes I couldn't find it again.

Removing a leading '. /' is of another trivial step. Your comment about 'no embedded .

Or ..' is also pertinent. It is actually surprisingly difficult to do the job properly - doubly so if any of the names is actually a symlink; we're both doing purely textual analysis. – Jonathan Leffler Apr 2 '10 at 6:28 @Dennis: Of course, unless you have Newcastle Connection networking, trying to get above root is futile, so ../../../.. and ../../.. are equivalent.

However, that is pure escapism; your criticism is correct. (Newcastle Connection allowed you to configure and use the notation /../host/path/on/remote/machine to get to a different host - a neat scheme. I believe it supported /../../network/host/path/on/remote/network/and/host too.It's on Wikipedia.

) – Jonathan Leffler Apr 2 '10 at 6:31 So instead, we now have the double slash of UNC. – Dennis Williamson Apr 2 '10 at 10:10 1 The "readlink" utility (at least the GNU version) can do the equivalent of realpath(), if you pass it the "-f" option. For example, on my system, readlink /usr/bin/vi gives /etc/alternatives/vi, but that's another symlink - whereas readlink -f /usr/bin/vi gives /usr/bin/vim.

Basic, which is the ultimate destination of all the symlinks... – psmears Apr 2 '107 at 9:54.

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 Testing: $ . /test. Sh TEST=/home/ubuntu/.

//blah TMP=/home/ubuntu/blah REL=blah.

1 for compactness and bash-ness. You should, however, also call readlink on $(pwd). – DevSolar Nov 29 '10 at 16:06 2 Relative does not mean file has to be placed in the same directory.

– macias Mar 26 at 15:47.

This script works only on the path names. It does not require any of the files to exist. If the paths passed are not absolute, the behavior is a bit unusual, but should work as expected if both paths are relative.

I only tested it on OSX, so it might not be portable. #! /bin/bash set -e declare SCRIPT_NAME="$(basename $0)" function usage { echo "Usage: $SCRIPT_NAME " echo " Outputs relative to " 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= "${target_part$i}" ; then echo -n "${target_part$i}" fi done #One last newline echo.

Also, the code is a bit copy & paste-ish, but I needed this rather quickly. – juancn Dec 6 '10 at 20:25.

I took your question as a challenge to write this in "portable" shell code, i.e. With a POSIX shell in mind no bashisms such as arrays avoid calling externals like the plague. There's not a single fork in the script!

That makes it blazingly fast, especially on systems with significant fork overhead, like cygwin. Must deal with glob characters in pathnames (*,? , , ) It runs on any POSIX conformant shell (zsh, bash, ksh, ash, busybox, ...).

It even contains a testsuite to verify its operation. Canonicalization of pathnames is left as an exercise. :-) #!

/bin/sh # Find common parent directory path for a pair of paths. # Call with two pathnames as args, e.g. # commondirpart foo/bar foo/baz/bat -> result="foo/" # The result is either empty or ends with "/". Commondirpart () { result="" while test ${#1} -gt 0 -a ${#2} -gt 0; do if test "${1%${1#?

}}"! = "${2%${2#? }}"; then # First characters the same?

Break # No, we're done comparing. Fi result="$result${1%${1#? }}" # Yes, append to result.

Set -- "${1#? }" "${2#? }" # Chop first char off both strings.

Done case "$result" in (""|*/) ;; (*) result="${result%/*}/";; esac } # Turn foo/bar/baz into ../../.. # dir2dotdot () { OLDIFS="$IFS" IFS="/" result="" for dir in $1; do result="$result../" done result="${result%/}" IFS="$OLDIFS" } # Call with FROM TO args. Relativepath () { case "$1" in (*//*|*/. /*|*/../*|*?

/|*/. |*/..) printf '%s\n' "'$1' not canonical"; exit 1;; (/*) from="${1#? }";; (*) printf '%s\n' "'$1' not absolute"; exit 1;; esac case "$2" in (*//*|*/.

/*|*/../*|*? /|*/. |*/..) printf '%s\n' "'$2' not canonical"; exit 1;; (/*) to="${2#?

}";; (*) printf '%s\n' "'$2' not absolute"; exit 1;; esac case "$to" in ("$from") # Identical directories. Result=".";; ("$from"/*) # From /x to /x/foo/bar -> foo/bar result="${to##$from/}";; ("") # From /foo/bar to / -> ../.. dir2dotdot "$from";; (*) case "$from" in ("$to"/*) # From /x/foo/bar to /x -> ../.. dir2dotdot "${from##$to/}";; (*) # Everything else. Commondirpart "$from" "$to" common="$result" dir2dotdot "${from#$common}" result="$result/${to#$common}" esac ;; esac } set -f # noglob set -x cat /?

/? . /?

/? . /?

/? . /?

* /? * . /* /* .

/* /** ../** /* /*** ../*** /*. * /*. ** ../*.

** /*.? /*.? ../*.? / / . /a-z* /0-9* ../0-9* /foo /foo . /foo / .. /foo/bar / ../.. /foo/bar /foo .. /foo/bar /foo/baz ../baz /foo/bar /bar/foo ../../bar/foo /foo/bar/baz /gnarf/blurfl/blubb ../../../gnarf/blurfl/blubb /foo/bar/baz /gnarf ../../../gnarf /foo/bar/baz /foo/baz ../../baz /foo.

/bar. ../bar. EOF while read FROM TO VIA; do relativepath "$FROM" "$TO" printf '%s\n' "FROM: $FROM" "TO: $TO" "VIA: $result" if test "$result"!

= "$VIA"; then printf '%s\n' "OOOPS! Expected '$VIA' but got '$result'" fi done # vi: set tabstop=3 shiftwidth=3 expandtab fileformat=unix.

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

Here's a shell script that does it without calling other programs: #! /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 source: ynform.org/w/Pub/Relpath.

This is not really portable due to the use of the ${param/pattern/subst} construct, which is not POSIX (as of 2011). – Jens Aug 11 at 11:57 The referenced source ynform. Org/w/Pub/Relpath points to a completely garbled wiki page containing the script contents several times, intermingled with vi tilde lines, error messages about commands not found and whatnot.

Utterly useless for someone researching the original. – Jens Aug 11 at 15:02 wiki page is now fixed – pooryorick Sep 8 at 12:38.

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.

This is Extrememly Portable :) – Anonymous Oct 31 at 18:30.

I cant really gove you an answer,but what I can give you is a way to a solution, that is you have to find the anglde that you relate to or peaks your interest. A good paper is one that people get drawn into because it reaches them ln some way.As for me WW11 to me, I think of the holocaust and the effect it had on the survivors, their families and those who stood by and did nothing until it was too late.

Related Questions