Expand a possible relative path in bash

LinuxBashAbsolute Path

Linux Problem Overview


As arguments to my script there are some file paths. Those can, of course, be relative (or contain ~). But for the functions I've written I need paths that are absolute, but do not have their symlinks resolved.

Is there any function for this?

Linux Solutions


Solution 1 - Linux

MY_PATH=$(readlink -f $YOUR_ARG) will resolve relative paths like "./" and "../"

Consider this as well (source):

#!/bin/bash
dir_resolve()
{
cd "$1" 2>/dev/null || return $?  # cd to desired directory; if fail, quell any error messages but return exit status
echo "`pwd -P`" # output full, link-resolved path
}

# sample usage
if abs_path="`dir_resolve \"$1\"`"
then
echo "$1 resolves to $abs_path"
echo pwd: `pwd` # function forks subshell, so working directory outside function is not affected
else
echo "Could not reach $1"
fi

Solution 2 - Linux

http://www.linuxquestions.org/questions/programming-9/bash-script-return-full-path-and-filename-680368/page3.html has the following

function abspath {
    if [[ -d "$1" ]]
    then
        pushd "$1" >/dev/null
        pwd
        popd >/dev/null
    elif [[ -e "$1" ]]
    then
        pushd "$(dirname "$1")" >/dev/null
        echo "$(pwd)/$(basename "$1")"
        popd >/dev/null
    else
        echo "$1" does not exist! >&2
        return 127
    fi
}

which uses pushd/popd to get into a state where pwd is useful.

Solution 3 - Linux

Simple one-liner:

function abs_path {
  (cd "$(dirname '$1')" &>/dev/null && printf "%s/%s" "$PWD" "${1##*/}")
}

Usage:

function do_something {
    local file=$(abs_path $1)
    printf "Absolute path to %s: %s\n" "$1" "$file"
}
do_something $HOME/path/to/some\ where

I am still trying to figure out how I can get it to be completely oblivious to whether the path exists or not (so it can be used when creating files as well).

Solution 4 - Linux

This does the trick for me on OS X: $(cd SOME_DIRECTORY 2> /dev/null && pwd -P)

It should work anywhere. The other solutions seemed too complicated.

Solution 5 - Linux

If your OS supports it, use:

realpath -s "./some/dir"

And using it in a variable:

some_path="$(realpath -s "./some/dir")"

Which will expand your path. Tested on Ubuntu and CentOS, might not be available on yours. Some recommend readlink, but documentation for readlink says:

> Note realpath(1) is the preferred command to use for canonicalization functionality.

In case people wonder why I quote my variables, it's to preserve spaces in paths. Like doing realpath some path will give you two different path results. But realpath "some path" will return one. Quoted parameters ftw :)

Thanks to NyanPasu64 for the heads up. You'll want to add -s if you don't want it to follow the symlinks.

Solution 6 - Linux

Use readlink -f <relative-path>, e.g.

export FULLPATH=`readlink -f ./`

Solution 7 - Linux

on OS X you can use

stat -f "%N" YOUR_PATH

on linux you might have realpath executable. if not, the following might work (not only for links):

readlink -c YOUR_PATH

Solution 8 - Linux

Maybe this is more readable and does not use a subshell and does not change the current dir:

dir_resolve() {
  local dir=`dirname "$1"`
  local file=`basename "$1"`
  pushd "$dir" &>/dev/null || return $? # On error, return error code
  echo "`pwd -P`/$file" # output full, link-resolved path with filename
  popd &> /dev/null
}

Solution 9 - Linux

There's another method. You can use python embedding in bash script to resolve a relative path.

abs_path=$(python3 - <<END
from pathlib import Path
path = str(Path("$1").expanduser().resolve())
print(path)
END
)

Solution 10 - Linux

self edit, I just noticed the OP said he's not looking for symlinks resolved:

"But for the functions I've written I need paths that are absolute, but do not have their symlinks resolved."

So guess this isn't so apropos to his question after all. :)

Since I've run into this many times over the years, and this time around I needed a pure bash portable version that I could use on OSX and linux, I went ahead and wrote one:

The living version lives here:

https://github.com/keen99/shell-functions/tree/master/resolve_path

but for the sake of SO, here's the current version (I feel it's well tested..but I'm open to feedback!)

Might not be difficult to make it work for plain bourne shell (sh), but I didn't try...I like $FUNCNAME too much. :)

#!/bin/bash

resolve_path() {
	#I'm bash only, please!
	# usage:  resolve_path <a file or directory> 
	# follows symlinks and relative paths, returns a full real path
	#
	local owd="$PWD"
	#echo "$FUNCNAME for $1" >&2
	local opath="$1"
	local npath=""
	local obase=$(basename "$opath")
	local odir=$(dirname "$opath")
	if [[ -L "$opath" ]]
	then
	#it's a link.
	#file or directory, we want to cd into it's dir
		cd $odir
	#then extract where the link points.
		npath=$(readlink "$obase")
		#have to -L BEFORE we -f, because -f includes -L :(
		if [[ -L $npath ]]
		 then
		#the link points to another symlink, so go follow that.
			resolve_path "$npath"
			#and finish out early, we're done.
			return $?
			#done
		elif [[ -f $npath ]]
		#the link points to a file.
		 then
			#get the dir for the new file
			nbase=$(basename $npath)
		 	npath=$(dirname $npath)
		 	cd "$npath"
		 	ndir=$(pwd -P)
		 	retval=0
		 	#done
		elif [[ -d $npath ]]
		 then
		#the link points to a directory.
			cd "$npath"
			ndir=$(pwd -P)
			retval=0
			#done
		else
			echo "$FUNCNAME: ERROR: unknown condition inside link!!" >&2
			echo "opath [[ $opath ]]" >&2
			echo "npath [[ $npath ]]" >&2
			return 1
		fi
	else
		if ! [[ -e "$opath" ]]
		 then
			echo "$FUNCNAME: $opath: No such file or directory" >&2
			return 1
			#and break early
		elif [[ -d "$opath" ]]
		 then 
			cd "$opath"
			ndir=$(pwd -P)
			retval=0
			#done
		elif [[ -f "$opath" ]]
		 then
		 	cd $odir
		 	ndir=$(pwd -P)
		 	nbase=$(basename "$opath")
		 	retval=0
		 	#done
		else
			echo "$FUNCNAME: ERROR: unknown condition outside link!!" >&2
			echo "opath [[ $opath ]]" >&2
			return 1
		fi
	fi
	#now assemble our output
	echo -n "$ndir"
	if [[ "x${nbase:=}" != "x" ]]
	 then
	 	echo "/$nbase"
	else 
		echo
	fi
	#now return to where we were
	cd "$owd"
	return $retval
}

here's a classic example, thanks to brew:

%% ls -l `which mvn`
lrwxr-xr-x  1 draistrick  502  29 Dec 17 10:50 /usr/local/bin/mvn@ -> ../Cellar/maven/3.2.3/bin/mvn

use this function and it will return the -real- path:

%% cat test.sh
#!/bin/bash
. resolve_path.inc
echo
echo "relative symlinked path:"
which mvn
echo
echo "and the real path:"
resolve_path `which mvn`


%% test.sh

relative symlinked path:
/usr/local/bin/mvn

and the real path:
/usr/local/Cellar/maven/3.2.3/libexec/bin/mvn

Solution 11 - Linux

Do you have to use bash exclusively? I needed to do this and got fed up with differences between Linux and OS X. So I used PHP for a quick and dirty solution.

#!/usr/bin/php <-- or wherever
<?php
{
   if($argc!=2)
      exit();
   $fname=$argv[1];
   if(!file_exists($fname))
      exit();
   echo realpath($fname)."\n";
}
?>

I know it's not a very elegant solution but it does work.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionlaarView Question on Stackoverflow
Solution 1 - LinuxAmir AfghaniView Answer on Stackoverflow
Solution 2 - LinuxMike SamuelView Answer on Stackoverflow
Solution 3 - LinuxandsensView Answer on Stackoverflow
Solution 4 - LinuxMikeyView Answer on Stackoverflow
Solution 5 - LinuxRaidView Answer on Stackoverflow
Solution 6 - LinuxisapirView Answer on Stackoverflow
Solution 7 - LinuxVitaly KushnerView Answer on Stackoverflow
Solution 8 - LinuxsjaView Answer on Stackoverflow
Solution 9 - LinuxDmitryView Answer on Stackoverflow
Solution 10 - LinuxkeenView Answer on Stackoverflow
Solution 11 - LinuxDavid GView Answer on Stackoverflow