Is there a command to retrieve the absolute path given the relative path?
For example I want $line to contain the absolute path of each file in dir ./etc/
find ./ -type f | while read line; do
echo $line
done
Is there a command to retrieve the absolute path given the relative path?
For example I want $line to contain the absolute path of each file in dir ./etc/
find ./ -type f | while read line; do
echo $line
done
Try realpath
.
~ $ sudo apt-get install realpath # may already be installed
~ $ realpath .bashrc
/home/username/.bashrc
To avoid expanding symlinks, use realpath -s
.
The answer comes from "bash/fish command to print absolute path to a file".
#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
UPD Some explanations
"$1"
dirname "$1"
cd "$(dirname "$1")
into this relative dir and get absolute path for it by running pwd
shell command$(basename "$1")
echo
itFor what it's worth, I voted for the answer that was picked, but wanted to share a solution. The downside is, it's Linux only - I spent about 5 minutes trying to find the OSX equivalent before coming to Stack overflow. I'm sure it's out there though.
On Linux you can use readlink -e
in tandem with dirname
.
$(dirname $(readlink -e ../../../../etc/passwd))
yields
/etc/
And then you use dirname
's sister, basename
to just get
the filename
$(basename ../../../../../passwd)
yields
passwd
Put it all together..
F=../../../../../etc/passwd
echo "$(dirname $(readlink -e $F))/$(basename $F)"
yields
/etc/passwd
You're safe if you're targeting a directory, basename
will return nothing
and you'll just end up with double slashes in the final output.
realpath
is probably best
But ...
The initial question was very confused to start with, with an example poorly related to the question as stated.
The selected answer actually answers the example given, and not at all the question in the title. The first command is that answer (is it really ? I doubt), and could do as well without the '/'. And I fail to see what the second command is doing.
Several issues are mixed :
changing a relative pathname into an absolute one, whatever it
denotes, possibly nothing.
(Typically, if you issue a command such as touch foo/bar
, the
pathname foo/bar
must exist for you, and possibly be used in
computation, before the file is actually created.)
there may be several absolute pathname that denote the same file (or potential file), notably because of symbolic links (symlinks) on the path, but possibly for other reasons (a device may be mounted twice as read-only). One may or may not want to resolve explicity such symlinks.
getting to the end of a chain of symbolic links to a non-symlink file or name. This may or may not yield an absolute path name, depending on how it is done. And one may, or may not want to resolve it into an absolute pathname.
The command readlink foo
without option gives an answer only if its
argument foo
is a symbolic link, and that answer is the value of that
symlink. No other link is followed. The answer may be a relative path:
whatever was the value of the symlink argument.
However, readlink
has options (-f -e or -m) that will work for all
files, and give one absolute pathname (the one with no symlinks) to
the file actually denoted by the argument.
This works fine for anything that is not a symlink, though one might
desire to use an absolute pathname without resolving the intermediate
symlinks on the path. This is done by the command realpath -s foo
In the case of a symlink argument, readlink
with its options will
again resolve all symlinks on the absolute path to the argument, but
that will also include all symlinks that may be encountered by
following the argument value. You may not want that if you desired an
absolute path to the argument symlink itself, rather than to whatever
it may link to. Again, if foo
is a symlink, realpath -s foo
will
get an absolute path without resolving symlinks, including the one
given as argument.
Without the -s
option, realpath
does pretty much the same as
readlink
, except for simply reading the value of a link, as well as several
other things. It is just not clear to me why readlink
has its
options, creating apparently an undesirable redundancy with
realpath
.
Exploring the web does not say much more, except that there may be some variations across systems.
Conclusion : realpath
is the best command to use, with the most
flexibility, at least for the use requested here.
My favourite solution was the one by @EugenKonkov because it didn't imply the presence of other utilities (the coreutils package).
But it failed for the relative paths "." and "..", so here is a slightly improved version handling these special cases.
It still fails if the user doesn't have the permission to cd
into the parent directory of the relative path, though.
#! /bin/sh
# Takes a path argument and returns it as an absolute path.
# No-op if the path is already absolute.
function to-abs-path {
local target="$1"
if [ "$target" == "." ]; then
echo "$(pwd)"
elif [ "$target" == ".." ]; then
echo "$(dirname "$(pwd)")"
else
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"
fi
}
The best solution, imho, is the one posted here: https://stackoverflow.com/a/3373298/9724628.
It does require python to work, but it seems to cover all or most of the edge cases and be very portable solution.
python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file
python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file
If you are using bash on Mac OS X which neither has realpath existed nor its readlink can print the absolute path, you may have choice but to code your own version to print it. Here is my implementation:
(pure bash)
abspath(){
local thePath
if [[ ! "$1" =~ ^/ ]];then
thePath="$PWD/$1"
else
thePath="$1"
fi
echo "$thePath"|(
IFS=/
read -a parr
declare -a outp
for i in "${parr[@]}";do
case "$i" in
''|.) continue ;;
..)
len=${#outp[@]}
if ((len==0));then
continue
else
unset outp[$((len-1))]
fi
;;
*)
len=${#outp[@]}
outp[$len]="$i"
;;
esac
done
echo /"${outp[*]}"
)
}
(use gawk)
abspath_gawk() {
if [[ -n "$1" ]];then
echo $1|gawk '{
if(substr($0,1,1) != "/"){
path = ENVIRON["PWD"]"/"$0
} else path = $0
split(path, a, "/")
n = asorti(a, b,"@ind_num_asc")
for(i in a){
if(a[i]=="" || a[i]=="."){
delete a[i]
}
}
n = asorti(a, b, "@ind_num_asc")
m = 0
while(m!=n){
m = n
for(i=1;i<=n;i++){
if(a[b[i]]==".."){
if(b[i-1] in a){
delete a[b[i-1]]
delete a[b[i]]
n = asorti(a, b, "@ind_num_asc")
break
} else exit 1
}
}
}
n = asorti(a, b, "@ind_num_asc")
if(n==0){
printf "/"
} else {
for(i=1;i<=n;i++){
printf "/"a[b[i]]
}
}
}'
fi
}
(pure bsd awk)
#!/usr/bin/env awk -f
function abspath(path, i,j,n,a,b,back,out){
if(substr(path,1,1) != "/"){
path = ENVIRON["PWD"]"/"path
}
split(path, a, "/")
n = length(a)
for(i=1;i<=n;i++){
if(a[i]==""||a[i]=="."){
continue
}
a[++j]=a[i]
}
for(i=j+1;i<=n;i++){
delete a[i]
}
j=0
for(i=length(a);i>=1;i--){
if(back==0){
if(a[i]==".."){
back++
continue
} else {
b[++j]=a[i]
}
} else {
if(a[i]==".."){
back++
continue
} else {
back--
continue
}
}
}
if(length(b)==0){
return "/"
} else {
for(i=length(b);i>=1;i--){
out=out"/"b[i]
}
return out
}
}
BEGIN{
if(ARGC>1){
for(k=1;k<ARGC;k++){
print abspath(ARGV[k])
}
exit
}
}
{
print abspath($0)
}
example:
$ abspath I/am/.//..//the/./god/../of///.././war
/Users/leon/I/the/war
An improvement to @ernest-a's rather nice version:
absolute_path() {
cd "$(dirname "$1")"
case $(basename $1) in
..) echo "$(dirname $(pwd))";;
.) echo "$(pwd)";;
*) echo "$(pwd)/$(basename $1)";;
esac
}
This deals correctly with the case where the last element of the path is ..
, in which case the "$(pwd)/$(basename "$1")"
in @ernest-a's answer will come through as accurate_sub_path/spurious_subdirectory/..
.
You could use bash string substitution for any relative path $line:
line=$(echo ${line/#..\//`cd ..; pwd`\/})
line=$(echo ${line/#.\//`pwd`\/})
echo $line
The basic front-of-string substitution follows the formula
${string/#substring/replacement}
which is discussed well here: https://www.tldp.org/LDP/abs/html/string-manipulation.html
The \
character negates the /
when we want it to be part of the string that we find/replace.
I found Eugen Konkov's answer to be the best as it doesn't require installing any program. However, it will fail for non-existent directories.
I wrote a function that works for non-existent directories:
function getRealPath()
{
local -i traversals=0
currentDir="$1"
basename=''
while :; do
[[ "$currentDir" == '.' ]] && { echo "$1"; return 1; }
[[ $traversals -eq 0 ]] && pwd=$(cd "$currentDir" 2>&1 && pwd) && { echo "$pwd/$basename"; return 0; }
currentBasename="$(basename "$currentDir")"
currentDir="$(dirname "$currentDir")"
[[ "$currentBasename" == '..' ]] && (( ++traversals )) || { [[ traversals -gt 0 ]] && (( traversals-- )) || basename="$currentBasename/$basename"; }
done
}
It solves the problem of non-existent directories by traversing up with dirname
until cd
succeeds, then returning the current directory plus everything that was removed by dirname
.
This is a chained solution from all others, for example, when realpath
fails, either because it is not installed or because it exits with error code, then, the next solution is attempted until it get the path right.
#!/bin/bash
function getabsolutepath() {
local target;
local changedir;
local basedir;
local firstattempt;
target="${1}";
if [ "$target" == "." ];
then
printf "%s" "$(pwd)";
elif [ "$target" == ".." ];
then
printf "%s" "$(dirname "$(pwd)")";
else
changedir="$(dirname "${target}")" && basedir="$(basename "${target}")" && firstattempt="$(cd "${changedir}" && pwd)" && printf "%s/%s" "${firstattempt}" "${basedir}" && return 0;
firstattempt="$(readlink -f "${target}")" && printf "%s" "${firstattempt}" && return 0;
firstattempt="$(realpath "${target}")" && printf "%s" "${firstattempt}" && return 0;
# If everything fails... TRHOW PYTHON ON IT!!!
local fullpath;
local pythoninterpreter;
local pythonexecutables;
local pythonlocations;
pythoninterpreter="python";
declare -a pythonlocations=("/usr/bin" "/bin");
declare -a pythonexecutables=("python" "python2" "python3");
for path in "${pythonlocations[@]}";
do
for executable in "${pythonexecutables[@]}";
do
fullpath="${path}/${executable}";
if [[ -f "${fullpath}" ]];
then
# printf "Found ${fullpath}\\n";
pythoninterpreter="${fullpath}";
break;
fi;
done;
if [[ "${pythoninterpreter}" != "python" ]];
then
# printf "Breaking... ${pythoninterpreter}\\n"
break;
fi;
done;
firstattempt="$(${pythoninterpreter} -c "import os, sys; print( os.path.abspath( sys.argv[1] ) );" "${target}")" && printf "%s" "${firstattempt}" && return 0;
# printf "Error: Could not determine the absolute path!\\n";
return 1;
fi
}
printf "\\nResults:\\n%s\\nExit: %s\\n" "$(getabsolutepath "./asdfasdf/ asdfasdf")" "${?}"
Here's a rather short function that can be used to fully absolutize and canonicalize any given input path using only POSIX shell and readlink
semantics:
canonicalize_path() {
(
FILEPATH="$1"
for _ in 1 2 3 4 5 6 7 8; # Maximum symlink recursion depth
do
cd -L "`case "${FILEPATH}" in */*) echo "${FILEPATH%/*}";; *) echo ".";; esac`/" # cd $(dirname)
if ! FILEPATH="$(readlink "${FILEPATH##*/}" || ( echo "${FILEPATH##*/}" && false ) )";
then
break
fi
done
cd -P "." || return $?
echo "$(pwd)/${FILEPATH}"
)
}
If the referenced file does not exist only the directory path leading up to the final filename will be resolved. If the any of the directories leading up to the file path does not exist a cd
error will be returned. This happens to be the exact semantics of the GNU/Linux readlink -f
command it tries to mimic.
In bash/zsh you can also compact the list of numbers to just {1..8}
or similar. The number of 8 was chosen as this was maximum limit in Linux for many years before the changed it a total limit of 40 resolution for the entire path in version 4.2. If the resolution limit is reached the code will not fail, but instead return last considered path instead – an explicit [ -L "${FILEPATH}" ]
check could be added to detect this condition however.
This code can also be easily reused for ensuring the current working directory matches the executed script's location in the filesystem (a common requirement for shell scripts), by simply removing the function and subshell wrapper:
FILEPATH="$0"
for _ in 1 2 3 4 5 6 7 8; # Maximum symlink recursion depth
do
cd -L "`case "${FILEPATH}" in */*) echo "${FILEPATH%/*}";; *) echo ".";; esac`/" # cd $(dirname)
if ! FILEPATH="$(readlink "${FILEPATH##*/}" || ( echo "${FILEPATH##*/}" && false ) )";
then
break
fi
done
cd -P "."
FILEPATH="$(pwd)/${FILEPATH}"
Based on this answer by @EugenKonkov and this answer by @HashChange, my answer combines the brevity of the former with the handling of .
and ..
of the latter. I believe that all the options below rely upon nothing more than the basic Shell Command Language POSIX standards.
Using dirname
and basename
, an option is:
absPathDirname()
{
[ -d "${1}" ] && set -- "${1}" || set -- "`dirname "${1}"`" "/`basename "${1}"`"
echo "`cd "${1}"; pwd`${2}";
}
Without using dirname
or basename
, another brief option is:
absPathMinusD()
{
[ -d "${1}" ] && set -- "${1}" || set -- "${1%${1##*/}}" "/${1##*/}"
echo "`cd "${1:-.}"; pwd`${2}";
}
I would recommend one of the two options above, the rest are just for fun...
Grep version:
absPathGrep()
{
echo "`[ "${1##/*}" ] && echo "$1" | grep -Eo '^(.*/)?\.\.($|/)' | { read d && cd "$d"; echo "${PWD}/${1#$d}"; } || echo "$1"`"
}
As an interesting example of "what can be done with the shell's limited RegEx":
absPathShellReplace()
{
E="${1##*/}"; D="${E#$E${E#.}}"; DD="${D#$D${D#..}}"
DIR="${1%$E}${E#$DD}"; FILE="${1#$DIR}"; SEP=${FILE:+/}
echo "`cd "${DIR:-.}"; pwd`${SEP#$DIR}$FILE"
}