Question

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
Was it helpful?

Solution

use:

find $(pwd)/ -type f

to get all files or

echo $(pwd)/$line

to display full path (if relative path matters to)

OTHER TIPS

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".

If you have the coreutils package installed you can generally use readlink -f relative_file_name in order to retrieve the absolute one (with all symlinks resolved)

#! /bin/sh
echo "$(cd "$(dirname "$1")"; pwd)/$(basename "$1")"

UPD Some explanations

  1. This script get relative path as argument "$1"
  2. Then we get dirname part of that path (you can pass either dir or file to this script): dirname "$1"
  3. Then we cd "$(dirname "$1") into this relative dir and get absolute path for it by running pwd shell command
  4. After that we append basename to absolute path: $(basename "$1")
  5. As final step we echo it

For 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.

I think this is the most portable:

abspath() {                                               
    cd "$(dirname "$1")"
    printf "%s/%s\n" "$(pwd)" "$(basename "$1")"
    cd "$OLDPWD"
}

It will fail if the path does not exist though.

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
}

Eugen's answer didn't quite work for me but this did:

    absolute="$(cd $(dirname \"$file\"); pwd)/$(basename \"$file\")"

Side note, your current working directory is unaffected.

In case of find, it's probably easiest to just give the absolute path for it to search in, e.g.:

find /etc
find `pwd`/subdir_of_current_dir/ -type f

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

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.

  1. With resolving symlinks:
python -c "import os,sys; print os.path.realpath(sys.argv[1])" path/to/file
  1. or without it:
python -c "import os,sys; print os.path.abspath(sys.argv[1])" path/to/file

What they said, except find $PWD or (in bash) find ~+ is a bit more convenient.

Similar to @ernest-a's answer but without affecting $OLDPWD or define a new function you could fire a subshell (cd <path>; pwd)

$ pwd
/etc/apache2
$ cd ../cups 
$ cd -
/etc/apache2
$ (cd ~/..; pwd)
/Users
$ cd -
/etc/cups

If the relative path is a directory path, then try mine, should be the best:

absPath=$(pushd ../SOME_RELATIVE_PATH_TO_Directory > /dev/null && pwd && popd > /dev/null)

echo $absPath
echo "mydir/doc/ mydir/usoe ./mydir/usm" |  awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'

If you want to transform a variable containing a relative path into an absolute one, this works :

   dir=`cd "$dir"`

"cd" echoes without changing the working directory, because executed here in a sub-shell.

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")" "${?}"
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top