233

Is there a command to retrieve the absolute path given a 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
Roly
  • 2,126
  • 2
  • 20
  • 34
nubme
  • 2,857
  • 8
  • 29
  • 25
  • 8
    possible duplicate of [Converting relative path into absolute path](http://stackoverflow.com/questions/4045253/converting-relative-path-into-absolute-path) or [this](http://stackoverflow.com/questions/2564634/bash-convert-absolute-path-into-relative-path-given-a-current-directory). – Dennis Williamson Nov 13 '10 at 23:41
  • 1
    possible duplicate of [bash/fish command to print absolute path to a file](http://stackoverflow.com/questions/3915040/bash-fish-command-to-print-absolute-path-to-a-file) – Trevor Boyd Smith May 05 '15 at 16:14
  • A much better solution than any of the ones listed so far is here [how-to-convert-relative-path-to-absolute-path-in-unix](https://stackoverflow.com/questions/11246189/how-to-convert-relative-path-to-absolute-path-in-unix) – Daniel Genin Sep 26 '19 at 21:54
  • You may want to see [this](https://stackoverflow.com/a/957978/7032856) for it may be of use to configure paths in your scripts relative to repo path when in a git repo. – Nae Jun 04 '21 at 09:08

26 Answers26

264

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

Asclepius
  • 57,944
  • 17
  • 167
  • 143
epere4
  • 2,892
  • 2
  • 12
  • 8
146

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)

Moritz Bunkus
  • 11,592
  • 3
  • 37
  • 49
  • 4
    The behaviour for this is a little different from what the user asks, it will also follow and resolve recursive symlinks anywhere in the path. You might not want that in some cases. – ffledgling Oct 21 '16 at 13:28
  • 2
    @BradPeabody This does work in a Mac if you install coreutils from homebrew `brew install coreutils`. However the executable is prepended by a g: `greadlink -f relative_file_name` – Miguel Isla Jun 08 '18 at 08:10
  • 3
    Notice that the manual page of readlink(1) has as the first sentence of its description: "Note realpath(1) is the preferred command to use for canonicalization functionality." – josch Jun 13 '18 at 05:47
  • 1
    you can use -e instead of -f to check if the file/directory exists or not – Iceman Oct 17 '18 at 18:23
  • 1
    Completely fails with something as elementary as ".". – Szczepan Hołyszewski Jan 24 '22 at 22:30
  • Preferable solution is within shell and not requiring external software – Bill Gale Dec 20 '22 at 20:55
115
#! /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
Eugen Konkov
  • 22,193
  • 17
  • 108
  • 158
  • 7
    readlink is the simple solution for linux, but this solution works on OSX too, so +1 – thetoolman Aug 21 '17 at 23:58
  • 1
    This script is not equivalent to what `realpath` or `readlink -f` do. For instance, it doesn't work on paths where the last component is a symlink. – josch Jun 13 '18 at 05:50
  • 6
    @josch: The question is not about symlink resolving. But if you want to do that you can provide `-P` option to `pwd` command: `echo "$(cd "$(dirname "$1")"; pwd -P)/$(basename "$1")"` – Eugen Konkov Jun 13 '18 at 07:10
  • 9
    I like the answer, but it only works, if the user is allowed to cd into the directory. This might not always be possible. – Matthias B Jun 22 '18 at 10:28
  • 1
    My personal favourite :) Unfortunately, it failed when I fed it "." and ".." as relative paths. Slightly improved version: https://stackoverflow.com/a/51264222/508355 – hashchange Jul 10 '18 at 11:40
  • `echo "$(cd ${1%/*}; pwd)/${1##*/}"` Some similar with string manipulation if rel path in `$1`. – László Szilágyi Mar 18 '21 at 07:57
  • 1
    Doesn't work correctly with paths that are already absolute. – Szczepan Hołyszewski Jan 24 '22 at 22:33
  • Why is this using $1? On my system it largely fails with $1 and gets many of the issues reported in the comments above. If I use $0 it works perfectly for any situation I have tried. This makes sense as $1 is an empty string and $0 contains the path used to start the script – MikeKulls Jun 28 '22 at 00:08
  • 1
    @MikeKulls: This script expect that you provide parameter. A relative path to somewhere. – Eugen Konkov Jun 28 '22 at 09:23
  • @EugenKonkov ah, makes sense. I was focused on my particular use case, which was to find the path of my own script – MikeKulls Jun 30 '22 at 01:09
83

use:

find "$(pwd)"/ -type f

to get all files or

echo "$(pwd)/$line"

to display full path (if relative path matters to)

mpapis
  • 52,729
  • 14
  • 121
  • 158
38

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
synthesizerpatel
  • 27,321
  • 5
  • 74
  • 91
  • Excellent entry with `dirname`, `readlink`, and `basename`. That helped me to get the absolute path of a symbolic link -- not its target. – kevinarpe Nov 19 '13 at 14:52
  • Doesn't work when you want to return path to symbolic links (which I just happen to need to do...). – Tomáš Zato Apr 21 '15 at 19:01
  • How could you ever find the absolute path to a path that doesn't exist? – synthesizerpatel Jan 25 '16 at 17:33
  • @synthesizerpatel Quite easily, I would have thought; if I'm in `/home/GKFX` and I type `touch newfile`, then before I press enter you could work out that I mean "create /home/GKFX/newfile", which is an absolute path to a file that doesn't exist yet. – GKFX Sep 24 '17 at 18:22
29

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.

Ernest A
  • 7,526
  • 8
  • 34
  • 40
  • 4
    There's no need to cd back again. See http://stackoverflow.com/a/21188136/1504556. Your's is the best answer on this page, IMHO. For those interested the link gives an explanation as to *why* this solution works. – peterh Jan 26 '14 at 11:54
  • This is not very portable, `dirname` is a GNU core utility, not common to all unixen I believe. – einpoklum Jun 20 '17 at 13:47
  • @einpoklum `dirname` is a POSIX standard utility, see here: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html – Ernest A Jun 20 '17 at 14:51
  • Oh my God, thank you. I've been trying to fix the version that uses `${1##*/}` for a day now, and now that I replaced that trash with `basename "$1"` it seems to finally be properly handling paths that end in /. – l3l_aze Aug 28 '18 at 19:05
  • NB, this doesn't do the right thing with a path ending in `../..` – Alex Coventry Dec 27 '19 at 00:46
  • I [made a version which deals with that](https://stackoverflow.com/a/59494936/1941213). – Alex Coventry Dec 27 '19 at 07:01
  • What if `pushd` and `popd`? – Artfaith Apr 13 '23 at 15:58
19

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.

Eugen Konkov
  • 22,193
  • 17
  • 108
  • 158
babou
  • 632
  • 8
  • 10
  • man page of `readlink`says: "Note realpath(1) is the preferred command to use for canonicalization functionality." – jarno Jan 17 '22 at 07:42
  • @jarno Interesting. My man page does say that, but dates from April 2021. Any idea when that line was included? – babou Jan 18 '22 at 16:35
  • The [commit](https://github.com/coreutils/coreutils/commit/99deaff7e804298cecc326142bbe7263e2576224) was made in 2017. – jarno Jan 18 '22 at 16:46
  • @jarno Thanks. I should learn to search for that kind of info. Well, it is nice to have an answer that gets officially supported after 4 years :-). Too bad there is no badge for that. – babou Jan 18 '22 at 22:11
17

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
}
hashchange
  • 7,029
  • 1
  • 45
  • 41
  • +1 Nice!! But has a bug in this use-case: to-abs-path /tmp/../tmp/../var //var For comparison, the proper expected output would be: realpath /tmp/../tmp/../var /var – Dmitry Shevkoplyas Jun 28 '22 at 17:53
9

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.

Eugen Konkov
  • 22,193
  • 17
  • 108
  • 158
biomiker
  • 3,108
  • 1
  • 29
  • 26
4

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
hahnec
  • 552
  • 5
  • 12
MrSegFaulty
  • 535
  • 6
  • 10
4

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

Alex Coventry
  • 68,681
  • 4
  • 36
  • 40
3

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
Meow
  • 4,341
  • 1
  • 18
  • 17
  • readlink can handle missing files/folders just file: readlink -m I/am/.//..//the/./god/../of///.././war Yields same (as yours) output: /home/dmitry/I/the/war – Dmitry Shevkoplyas Nov 04 '22 at 15:25
3

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
Arkku
  • 41,011
  • 10
  • 62
  • 84
2

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
fakedrake
  • 6,528
  • 8
  • 41
  • 64
2

I was unable to find a solution that was neatly portable between Mac OS Catalina, Ubuntu 16 and Centos 7, so I decided to do it with python inline and it worked well for my bash scripts.

to_abs_path() {
  python -c "import os; print os.path.abspath('$1')"
}

to_abs_path "/some_path/../secrets"
openCivilisation
  • 796
  • 1
  • 8
  • 25
2

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.

Dan Bray
  • 7,242
  • 3
  • 52
  • 70
1

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
1
echo "mydir/doc/ mydir/usoe ./mydir/usm" |  awk '{ split($0,array," "); for(i in array){ system("cd "array[i]" && echo $PWD") } }'

In the snippet above awk is used to split the string of directories received from echo (via the pipeline) and creates an array.

Here is a step-by-step list of what is happening:

  • Space delimited list of directories is piped to awk
  • awk splits piped text into an array by spaces
  • awk loops over each item in the array (variable i)
  • awk uses the awk system command to run a cli command
  • The CLI command changes directory (cd) to the path specified by item "i" in the array and then prints (echo) the working directory using the environment variable $PWD"

Using cd to travel to each directory and then print the value of $PWD is a great way to have the OS do the work of determining an absolute path

Josh Barton
  • 148
  • 11
  • 3
    Thank you for this code snippet, which might provide some limited short-term help. A proper explanation [would greatly improve](//meta.stackexchange.com/q/114762) its long-term value by showing *why* this is a good solution to the problem, and would make it more useful to future readers with other, similar questions. Please [edit] your answer to add some explanation, including the assumptions you've made. – Toby Speight Feb 21 '18 at 09:17
1

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

Tobu
  • 24,771
  • 4
  • 91
  • 98
1

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.

  • Note that you could just use `dirname` & `basename`, I think. Something like `dir=\`dirname $line\`; file=\`basename $line\`; line=\`cd $dir; pwd\`/$file` – Alexis Wilke Jan 22 '21 at 18:56
1

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}"
ntninja
  • 1,204
  • 16
  • 20
1

BLUF:

cd $relative_path ; pwd

Here is an explanation that is POSIX compliant (I think), so it should work on any platform.

This is scriptable, of course, but I think breaking it down might make it easier for some people to understand / modify to a particular use case.

You can use which, locate, find, full paths, whatever.

x=your_file_name

$ x="nvi" 

file easily indicates symlinks

$ file -h `which $x`
/usr/local/bin/nvi: symbolic link to ../Cellar/nvi/1.81.6_5/bin/nvi

Next, munge the output a bit so we get a "complete" relative path.
We just need to remove the middle part in this example.

Note UPPERCASE Y vs lowercase x. There is probably a cleaner way to do this.

$ Y=$(file -h `which $x` | sed "s/$x: symbolic link to //")


$ echo $Y
/usr/local/bin/../Cellar/nvi/1.81.6_5/bin/nvi

Using dirname we get just the path portion. cd to it and the name should clean itself up.

$ cd `dirname $Y` ; pwd
/usr/local/Cellar/nvi/1.81.6_5/bin

That leads us to the old UNIX trick to "ghost walk" a directory by doing it all in parenthesis / a sub-shell. This effectively returns us to our current directory when done.

We can stick the actual file name back on the end for completeness.

ls even makes sure the absolute path is valid, for bonus points.

$ ( cd `dirname ${Y}` ; ls `pwd`/${x} )
/usr/local/Cellar/nvi/1.81.6_5/bin/nvi

So /usr/local/bin/nvi is really /usr/local/Cellar/nvi/1.81.6_5/bin/nvi

Simplified example to quickly "convert" a path:

$ (cd /usr/local/bin/../Cellar/nvi/1.81.6_5/bin ; pwd)
/usr/local/Cellar/nvi/1.81.6_5/bin

https://pubs.opengroup.org/onlinepubs/9699919799/utilities/file.html https://pubs.opengroup.org/onlinepubs/9699919799/utilities/dirname.html

Kajukenbo
  • 109
  • 1
  • 4
1

An update on previous answer using python 3

to_abs_path() {
  python -c "import os; print (os.path.abspath('$1'))"
}

Allowing

to_abs_path "./../some_file.txt"
Chris U
  • 53
  • 6
0

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.

Eric H.
  • 2,152
  • 4
  • 22
  • 34
  • 2
    On bash-4.3-p46, this doesn’t work: the shell prints an empty line when I run `dir=\`cd ".."\` && echo $dir` – Michael Sep 24 '16 at 03:05
0

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")" "${?}"
Evandro Coan
  • 8,560
  • 11
  • 83
  • 144
0

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"
}
Sean
  • 393
  • 2
  • 11