How do you normalize a file path in Bash?
Asked Answered
I

24

294

I want to transform /foo/bar/.. to /foo

Is there a bash command which does this?


Edit: in my practical case, the directory does exist.

Intent answered 12/11, 2008 at 17:16 Comment(4)
Does it matter if /foo/bar or even /foo actually exist, or are you only interested in the string manipulation aspect according to path name rules?Scilla
@twalberg ...that's kinda contrived...Reprise
@CamiloMartin Not contrived at all - it does exactly what the question asks - transforms /foo/bar/.. to /foo, and using a bash command. If there are other requirements that are not stated, then perhaps they should be...Tiffaneytiffani
@Tiffaneytiffani You've been doing too much TDD -_-'Reprise
K
303

if you're wanting to chomp part of a filename from the path, "dirname" and "basename" are your friends, and "realpath" is handy too.

dirname /foo/bar/baz 
# /foo/bar 
basename /foo/bar/baz
# baz
dirname $( dirname  /foo/bar/baz  ) 
# /foo 
realpath ../foo
# ../foo: No such file or directory
realpath /tmp/../tmp/../tmp
# /tmp

realpath alternatives

If realpath is not supported by your shell, you can try

readlink -f /path/here/.. 

Also

readlink -m /path/there/../../ 

Works the same as

realpath -s /path/here/../../

in that the path doesn't need to exist to be normalized.

Kurzawa answered 12/11, 2008 at 17:20 Comment(5)
For those of you what need a OS X solution, check out Adam Liss' answer below.Collagen
https://mcmap.net/q/13460/-how-can-i-set-the-current-working-directory-to-the-directory-of-the-script-in-bash This is a strongly related answer! I came across both of these QA posts in one day, and I wanted to link them together.Krishna
realpath appears to have been added to coreutils in 2012. See the file history at github.com/coreutils/coreutils/commits/master/src/realpath.c .Vaporizer
Both realpath and readlink comes from the GNU core utilities, so most probably you get either both or none. If I remember properly, the readlink version on Mac is slightly different than the GNU one :-\Kolk
Don't forget the -m option on realpath, which normalizes even if the path doesn't existCamphene
S
128

I don't know if there is a direct bash command to do this, but I usually do

normalDir="`cd "${dirToNormalize}";pwd`"
echo "${normalDir}"

and it works well.

Scrag answered 12/11, 2008 at 17:19 Comment(9)
This will normalize but not resolve soft links. This may be either a bug or a feature. :-)Greenish
It also has a problem if $CDPATH is defined; because the "cd foo" will switch into any "foo" directory that is a subdirectory of $CDPATH, not just a "foo" that's in the current directory. I think you need to do something like: CDPATH="" cd "${dirToNormalize}" && pwd -P.Adora
I did not know about CDPATH - that looks very neat! Your point is definitely something to keep in mind - I can imagine situations, though, where being able to reference the directory in CDPATH is exactly what you want. Shouldn't using a leading ./ fix it as well?Scrag
Tim's answer is definitely the simplest and most portable. CDPATH is easy to deal with: dir="$(unset CDPATH && cd "$dir" && pwd)"Grecoroman
This could be very dangerous (rm -rf $normalDir) if dirToNormalize does not exist!Cressler
Yeah, it's probably best to use an && as per @DavidBlevins's comment.Exploiter
This is the best answer for me because I'm working on a script that needs to be used on both OSX and Linux.Chaulmoogra
If for whatever reason you can't cd into the folder (e.g. no execute permissions), then pwd will return the original folder (which as Frank points out, could be very dangerous)Alleyne
Sudo cannot cd. If your execution requires sudo, this will be an issue.Syndesis
G
67

Try realpath. Below is the source in its entirety, hereby donated to the public domain.

// realpath.c: display the absolute path to a file or directory.
// Adam Liss, August, 2007
// This program is provided "as-is" to the public domain, without express or
// implied warranty, for any non-profit use, provided this notice is maintained.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libgen.h>   
#include <limits.h>

static char *s_pMyName;
void usage(void);

int main(int argc, char *argv[])
{
    char
        sPath[PATH_MAX];


    s_pMyName = strdup(basename(argv[0]));

    if (argc < 2)
        usage();

    printf("%s\n", realpath(argv[1], sPath));
    return 0;
}    

void usage(void)
{
    fprintf(stderr, "usage: %s PATH\n", s_pMyName);
    exit(1);
}
Greenish answered 12/11, 2008 at 17:18 Comment(15)
Is this included as part of the standard bash install? I'm getting "command not found" on our system (bash 3.00.15(1) on RHEL)Scrag
gnu.org/software/coreutils for readlink, but realpath comes from this package: packages.debian.org/unstable/utils/realpathKurzawa
Jay, Kent, thanks for checking on realpath. I wasn't aware a utility was available from debian; the above is far simpler and was developed independently. The Debian version provides additional functionality.Greenish
It's standard on ubuntu/BSD, not Centos/OSXWingfield
You should really strike through the part with "Bonus: its a bash command, and available everywhere!" part about realpath. It also happens to be my favourite, but instead of compling your tool from source. It's all to heavyweight, and most of the time you just want readlink -f... BTW, readlink is also NOT a bash builtin, but part of coreutils on Ubuntu.Palpitation
realpath does not work if the path (or the resulting path) would not exists -or- more specifically, if there is a path component that is a link to a non-existing path. realpath then can't resolve the dots.Forelady
What's the point of showing the code of main function in your update?Fixed
The point is that the code is an entire working example. Copy and paste it into a file, build it, and you have a bash command ... which is what the OP asked for.Greenish
You've said you're donating this to the public domain, but the header also says "for any non-profit use" – do you really mean to donate it to the public domain? That'd mean it can be used for commercial for-profit purposes as well as for non-profit…Workingman
I mean exactly what it says in the header, which I think is explicit in its restrictions and perfectly clear without playing games of semantics.Greenish
Steps to build one OS X: (1) Copy code from this page (2) Paste code into a file: pbpaste > realpath.c (3) build it: gcc -o realpath realpath.c (4) run it: ./realpath path/to/make/canonicalCollagen
Thank you for taking the time to post the source code for non-Debian / Ubuntu distros to utilize. It's wonderful that 9 years after invention a Q&A database has it direct from the author's keyboard.Feuar
You're very welcome! Glad this is still relevant and useful. It's interesting to see how coding has evolved now that we're more vigilant against buffer overflow attacks. If I were writing this today I'd be careful to check and limit the lengths of the args.Greenish
Arghh. usage statements are not errors messages. If the program is invoked incorrectly, you should emit a proper error message. If you choose to emit a usage statement when no arguments are passed, write it to stdout and return 0. If you choose to consider it an error, write an actual error message (eg, "no path specified") to stderr and return failure. There are few things more frustrating than being presented with 120 lines of usage statement rather than something like "option -O is invalid", and this little one-liner quickly grows to encourage precisely that problem.Nitrification
The question asked about just normalizing the path, without resolving symlinks.Rositaroskes
G
53

A portable and reliable solution is to use python, which is preinstalled pretty much everywhere (including Darwin). You have two options:

  1. abspath returns an absolute path but does not resolve symlinks:

    python -c "import os,sys; print(os.path.abspath(sys.argv[1]))" path/to/file

  2. realpath returns an absolute path and in doing so resolves symlinks, generating a canonical path:

    python -c "import os,sys; print(os.path.realpath(sys.argv[1]))" path/to/file

In each case, path/to/file can be either a relative or absolute path.

Girlie answered 30/7, 2010 at 16:8 Comment(3)
Thanks, that was the only one that worked. readlink or realpath are not available under OS X. Python should be on most platforms.Zemstvo
Just to clarify, readlink is available on OS X, just not with the -f option. Portable workarounds discussed here.Relational
It literally boggles my mind that this is the only sane solution if you don't want to follow links. The Unix way my FOOT.Tellus
M
36

Use the readlink utility from the coreutils package.

MY_PATH=$(readlink -f "$0")
Mislead answered 26/7, 2010 at 23:41 Comment(2)
BSD does not have the -f flag, meaning that this will fail even on latest MacOS Mojave and many other systems. Forget about using -f if you want portability, lots of OS-es are affected.Zemstvo
@sorin. The question is not about Mac, it's about Linux.Mislead
A
20

Old question, but there is much simpler way if you are dealing with full path names at the shell level:

   abspath="$( cd "$path" && pwd )"

As the cd happens in a subshell it does not impact the main script.

Two variations, supposing your shell built-in commands accept -L and -P, are:

   abspath="$( cd -P "$path" && pwd -P )"    #physical path with resolved symlinks
   abspath="$( cd -L "$path" && pwd -L )"    #logical path preserving symlinks

Personally, I rarely need this later approach unless I'm fascinated with symbolic links for some reason.

FYI: variation on obtaining the starting directory of a script which works even if the script changes it's current directory later on.

name0="$(basename "$0")";                  #base name of script
dir0="$( cd "$( dirname "$0" )" && pwd )"; #absolute starting dir

The use of CD assures you always have the absolute directory, even if the script is run by commands such as ./script.sh which, without the cd/pwd, often gives just .. Useless if the script does a cd later on.

Abide answered 14/3, 2016 at 19:49 Comment(0)
L
17

readlink is the bash standard for obtaining the absolute path. It also has the advantage of returning empty strings if paths or a path doesn't exist (given the flags to do so).

To get the absolute path to a directory that may or may not exist, but who's parents do exist, use:

abspath=$(readlink -f $path)

To get the absolute path to a directory that must exist along with all parents:

abspath=$(readlink -e $path)

To canonicalise the given path and follow symlinks if they happen to exist, but otherwise ignore missing directories and just return the path anyway, it's:

abspath=$(readlink -m $path)

The only downside is that readlink will follow links. If you do not want to follow links, you can use this alternative convention:

abspath=$(cd ${path%/*} && echo $PWD/${path##*/})

That will chdir to the directory part of $path and print the current directory along with the file part of $path. If it fails to chdir, you get an empty string and an error on stderr.

Lashaun answered 2/10, 2013 at 22:6 Comment(3)
readlink is a good option if it's available. OS X version does not support the -e or -f options. In the first three examples, you should have double-quotes around $path to handle spaces or wildcards in filename. +1 for parameter expansion, but this has a security vulnerability. If path is empty, this will cd to your home directory. You need double quotes. abspath=$(cd "${path%/*}" && echo "$PWD/${path##*/}")Dougdougal
This was just an example. If you are hell bent on security, then you really shouldn't be using bash or any other shell variant at all. Also, bash has its own issues when it comes to cross platform compatibility, as well as having issues with functionality change between major versions. OSX is just one of many platforms with issues pertinent to shell scripting, not to mention it is based on BSD. When you have to be truly multi platform, you need to be POSIX compliant, so parameter expansion really goes out of the window. Take a look at Solaris or HP-UX some time.Lashaun
Not meaning any offense here, but pointing out obscure issues such as this is important. I'm just wanting a quick answer to this trivial problem and I would have trusted that code with any/all input if it weren't for the comment above. It's also important to support OS-X in these bash discussions. There are a lot of commands that are unfortunately not supported on OS-X, and many forums take that for granted when discussing Bash which means we will continue to get a lot of cross-platform issues unless it's dealt with sooner rather than later.Satterlee
I
10

As Adam Liss noted realpath is not bundled with every distribution. Which is a shame, because it is the best solution. The provided source code is great, and I will probably start using it now. Here is what I have been using until now, which I share here just for completeness:

get_abs_path() {
     local PARENT_DIR=$(dirname "$1")
     cd "$PARENT_DIR"
     local ABS_PATH="$(pwd)"/"$(basename "$1")"
     cd - >/dev/null
     echo "$ABS_PATH"
} 

If you want it to resolve symlinks, just replace pwd with pwd -P.

Irreclaimable answered 18/6, 2011 at 2:38 Comment(1)
One gotcha with the pwd -P option for this case... Consider what would happen if $(basename "$1") was a symlink to a file in another directory. The pwd -P only resolves symlinks in the directory portion of the path, but not the basename portion.Dougdougal
I
8

My recent solution was:

pushd foo/bar/..
dir=`pwd`
popd

Based on the answer of Tim Whitcomb.

Irretentive answered 10/10, 2011 at 19:47 Comment(2)
I suspect this fails if the argument isn't a directory. Suppose I want to know where /usr/bin/java leads to?Refrigerator
If you know it's a file, you could give pushd $(dirname /usr/bin/java) a try.Irretentive
T
5

Not exactly an answer but perhaps a follow-up question (original question was not explicit):

readlink is fine if you actually want to follow symlinks. But there is also a use case for merely normalizing ./ and ../ and // sequences, which can be done purely syntactically, without canonicalizing symlinks. readlink is no good for this, and neither is realpath.

for f in $paths; do (cd $f; pwd); done

works for existing paths, but breaks for others.

A sed script would seem to be a good bet, except that you cannot iteratively replace sequences (/foo/bar/baz/../.. -> /foo/bar/.. -> /foo) without using something like Perl, which is not safe to assume on all systems, or using some ugly loop to compare the output of sed to its input.

FWIW, a one-liner using Java (JDK 6+):

jrunscript -e 'for (var i = 0; i < arguments.length; i++) {println(new java.io.File(new java.io.File(arguments[i]).toURI().normalize()))}' $paths
Trocki answered 29/5, 2012 at 23:16 Comment(5)
realpath has a -s option to not resolve symbolic links and only resolve references to /./, /../ and remove extra / characters. When combined with the -m option, realpath operates only on the file name, and does not touch any actual file. It sounds like the perfect solution. But alas, realpath is still missing on many systems.Dougdougal
Removing .. components cannot be done syntactically when symlinks are involved. /one/two/../three is not the same as /one/three if two is a symlink to /foo/bar.Balzer
@Balzer yes as I said in my response this is for a use case when symlink canonicalization is not wanted or needed.Trocki
@JesseGlick it's not just a case of whether or not you want to canonicalize symlinks. Your algorithm actually produces the wrong answer. For your answer to be correct, you would have to know a priori that there were no symlinks involved (or that they were only of a certain form). Your answer says that you don't want to canonicalize them, not that there are no symlinks involved in the path.Balzer
There are use cases where normalization must be performed without assuming any fixed, existing directory structure. URI normalization is similar. In these cases it is an inherent limitation that the result will not generally be correct if there happen to be symlinks near a directory where the result is later applied.Trocki
K
5

I'm late to the party, but this is the solution I've crafted after reading a bunch of threads like this:

resolve_dir() {
        (builtin cd `dirname "${1/#~/$HOME}"`'/'`basename "${1/#~/$HOME}"` 2>/dev/null; if [ $? -eq 0 ]; then pwd; fi)
}

This will resolve the absolute path of $1, play nice with ~, keep symlinks in the path where they are, and it won't mess with your directory stack. It returns the full path or nothing if it doesn't exist. It expects $1 to be a directory and will probably fail if it's not, but that's an easy check to do yourself.

Kuo answered 24/8, 2013 at 16:49 Comment(0)
H
4

Talkative, and a bit late answer. I need to write one since I'm stuck on older RHEL4/5. I handles absolute and relative links, and simplifies //, /./ and somedir/../ entries.

test -x /usr/bin/readlink || readlink () {
        echo $(/bin/ls -l $1 | /bin/cut -d'>' -f 2)
    }


test -x /usr/bin/realpath || realpath () {
    local PATH=/bin:/usr/bin
    local inputpath=$1
    local changemade=1
    while [ $changemade -ne 0 ]
    do
        changemade=0
        local realpath=""
        local token=
        for token in ${inputpath//\// }
        do 
            case $token in
            ""|".") # noop
                ;;
            "..") # up one directory
                changemade=1
                realpath=$(dirname $realpath)
                ;;
            *)
                if [ -h $realpath/$token ] 
                then
                    changemade=1
                    target=`readlink $realpath/$token`
                    if [ "${target:0:1}" = '/' ]
                    then
                        realpath=$target
                    else
                        realpath="$realpath/$target"
                    fi
                else
                    realpath="$realpath/$token"
                fi
                ;;
            esac
        done
        inputpath=$realpath
    done
    echo $realpath
}

mkdir -p /tmp/bar
(cd /tmp ; ln -s /tmp/bar foo; ln -s ../.././usr /tmp/bar/link2usr)
echo `realpath /tmp/foo`
Hollis answered 10/11, 2009 at 14:33 Comment(0)
L
4

The problem with realpath is that it is not available on BSD (or OSX for that matter). Here is a simple recipe extracted from a rather old (2009) article from Linux Journal, that is quite portable:

function normpath() {
  # Remove all /./ sequences.
  local path=${1//\/.\//\/}

  # Remove dir/.. sequences.
  while [[ $path =~ ([^/][^/]*/\.\./) ]]; do
    path=${path/${BASH_REMATCH[0]}/}
  done
  echo $path
}

Notice this variant also does not require the path to exist.

Limey answered 25/3, 2015 at 13:10 Comment(1)
This doesn’t resolve symlinks however. Realpath processes paths starting at the root and follows symlinks as it progresses. All this does is collapse parent references.Creaky
E
4

I made a builtin-only function to handle this with a focus on highest possible performance (for fun). It does not resolve symlinks, so it is basically the same as realpath -sm.

## A bash-only mimic of `realpath -sm`. 
## Give it path[s] as argument[s] and it will convert them to clean absolute paths
abspath () { 
  ${*+false} && { >&2 echo $FUNCNAME: missing operand; return 1; };
  local c s p IFS='/';  ## path chunk, absolute path, input path, IFS for splitting paths into chunks
  local -i r=0;         ## return value

  for p in "$@"; do
    case "$p" in        ## Check for leading backslashes, identify relative/absolute path
    '') ((r|=1)); continue;;
    //[!/]*)  >&2 echo "paths =~ ^//[^/]* are impl-defined; not my problem"; ((r|=2)); continue;;
    /*) ;;
    *)  p="$PWD/$p";;   ## Prepend the current directory to form an absolute path
    esac

    s='';
    for c in $p; do     ## Let IFS split the path at '/'s
      case $c in        ### NOTE: IFS is '/'; so no quotes needed here
      ''|.) ;;          ## Skip duplicate '/'s and '/./'s
      ..) s="${s%/*}";; ## Trim the previous addition to the absolute path string
      *)  s+=/$c;;      ### NOTE: No quotes here intentionally. They make no difference, it seems
      esac;
    done;

    echo "${s:-/}";     ## If xpg_echo is set, use `echo -E` or `printf $'%s\n'` instead
  done
  return $r;
}

Note: This function does not handle paths starting with //, as exactly two double slashes at the start of a path are implementation-defined behavior. However, it handles /, ///, and so on just fine.

This function seems to handle all edge cases properly, but there might still be some out there that I haven't dealt with.

Performance Note: when called with thousands of arguments, abspath runs about 10x slower than realpath -sm; when called with a single argument, abspath runs >110x faster than realpath -sm on my machine, mostly due to not needing to execute a new program every time.

Effuse answered 6/5, 2020 at 10:28 Comment(0)
S
3

Try our new Bash library product realpath-lib that we have placed on GitHub for free and unencumbered use. It's thoroughly documented and makes a great learning tool.

It resolves local, relative and absolute paths and doesn't have any dependencies except Bash 4+; so it should work just about anywhere. It's free, clean, simple and instructive.

You can do:

get_realpath <absolute|relative|symlink|local file path>

This function is the core of the library:

function get_realpath() {

if [[ -f "$1" ]]
then 
    # file *must* exist
    if cd "$(echo "${1%/*}")" &>/dev/null
    then 
        # file *may* not be local
        # exception is ./file.ext
        # try 'cd .; cd -;' *works!*
        local tmppwd="$PWD"
        cd - &>/dev/null
    else 
        # file *must* be local
        local tmppwd="$PWD"
    fi
else 
    # file *cannot* exist
    return 1 # failure
fi

# reassemble realpath
echo "$tmppwd"/"${1##*/}"
return 0 # success

}

It also contains functions to get_dirname, get_filename, get_ stemname and validate_path. Try it across platforms, and help to improve it.

Shalne answered 2/10, 2013 at 15:52 Comment(0)
N
2

Based on @Andre's answer, I might have a slightly better version, in case someone is after a loop-free, completely string-manipulation based solution. It is also useful for those who don't want to dereference any symlinks, which is the downside of using realpath or readlink -f.

It works on bash versions 3.2.25 and higher.

shopt -s extglob

normalise_path() {
    local path="$1"
    # get rid of /../ example: /one/../two to /two
    path="${path//\/*([!\/])\/\.\./}"
    # get rid of /./ and //* example: /one/.///two to /one/two
    path="${path//@(\/\.\/|\/+(\/))//}"
    # remove the last '/.'
    echo "${path%%/.}"
}

$ normalise_path /home/codemedic/../codemedic////.config
/home/codemedic/.config
Nippers answered 27/6, 2015 at 8:50 Comment(4)
This is a nice idea, but I wasted 20 minutes trying to get this to work on various different versions of bash. It turns out that the extglob shell option needs to be on for this to work, and it isn't by default. When it comes to bash functionality, it's important to specify both the required version and non-default options because these details can vary between OSs. For example a recent version of Mac OSX (Yosemite) only comes with an outdated version of bash (3.2).Mimimimic
Sorry @ricovox; I have now updated those. I am eager to know the exact version of Bash you have there. The above formula (updated) works on CentOS 5.8 which comes with bash 3.2.25Substation
Sorry for the confusion. This code DID work on my version of Mac OSX bash (3.2.57) once I had turned extglob on. My note about bash versions was a more general one (which actually applies more to another answer here regarding regex in bash).Mimimimic
I appreciate your answer though. I used it as a base for my own. Incidentally I noticed several cases where yours fails: (1) Relative paths hello/../world (2) Dots in filename /hello/..world (3) Dots after double-slash /hello//../world (4) Dot before or after double-slash /hello//./world or /hello/.//world (5) Parent after current: /hello/./../world/ (6) Parent after Parent: /hello/../../world , etc. -- Some of these can be fixed by using a loop to make corrections until the path stops changing. (Also remove dir/../, not /dir/.. but remove dir/.. from the end.)Mimimimic
S
2

If you just want to normalize a path, existed or not existed, without touching the file system, without resolving any links, and without external utils, here is a pure Bash function translated from Python's posixpath.normpath.

#!/usr/bin/env bash

# Normalize path, eliminating double slashes, etc.
# Usage: new_path="$(normpath "${old_path}")"
# Translated from Python's posixpath.normpath:
# https://github.com/python/cpython/blob/master/Lib/posixpath.py#L337
normpath() {
  local IFS=/ initial_slashes='' comp comps=()
  if [[ $1 == /* ]]; then
    initial_slashes='/'
    [[ $1 == //* && $1 != ///* ]] && initial_slashes='//'
  fi
  for comp in $1; do
    [[ -z ${comp} || ${comp} == '.' ]] && continue
    if [[ ${comp} != '..' || (-z ${initial_slashes} && ${#comps[@]} -eq 0) || (\
      ${#comps[@]} -gt 0 && ${comps[-1]} == '..') ]]; then
      comps+=("${comp}")
    elif ((${#comps[@]})); then
      unset 'comps[-1]'
    fi
  done
  comp="${initial_slashes}${comps[*]}"
  printf '%s\n' "${comp:-.}"
}

Examples:

new_path="$(normpath '/foo/bar/..')"
echo "${new_path}"
# /foo

normpath "relative/path/with trailing slashs////"
# relative/path/with trailing slashs

normpath "////a/../lot/././/mess////./here/./../"
# /lot/mess

normpath ""
# .
# (empty path resolved to dot)

Personally, I cannot understand why Shell, a language often used for manipulating files, doesn't offer basic functions to deal with paths. In python, we have nice libraries like os.path or pathlib, which offers a whole bunch of tools to extract filename, extension, basename, path segments, split or join paths, to get absolute or normalized paths, to determine relations between paths, to do everything without much brain. And they take care of edge cases, and they're reliable. In Shell, to do any of these, either we call external executables, or we have to reinvent wheels with these extremely rudimentary and arcane syntaxes...

Shannashannah answered 4/3, 2021 at 5:8 Comment(0)
G
1

I needed a solution that would do all three:

  • Work on a stock Mac. realpath and readlink -f are addons
  • Resolve symlinks
  • Have error handling

None of the answers had both #1 and #2. I added #3 to save others any further yak-shaving.

#!/bin/bash

P="${1?Specify a file path}"

[ -e "$P" ] || { echo "File does not exist: $P"; exit 1; }

while [ -h "$P" ] ; do
    ls="$(ls -ld "$P")"
    link="$(expr "$ls" : '.*-> \(.*\)$')"
    expr "$link" : '/.*' > /dev/null &&
        P="$link" ||
        P="$(dirname "$P")/$link"
done
echo "$(cd "$(dirname "$P")"; pwd)/$(basename "$P")"

Here is a short test case with some twisted spaces in the paths to fully exercise the quoting

mkdir -p "/tmp/test/ first path "
mkdir -p "/tmp/test/ second path "
echo "hello" > "/tmp/test/ first path / red .txt "
ln -s "/tmp/test/ first path / red .txt " "/tmp/test/ second path / green .txt "

cd  "/tmp/test/ second path "
fullpath " green .txt "
cat " green .txt "
Grecoroman answered 16/5, 2018 at 1:53 Comment(0)
R
0

Based on loveborg's excellent python snippet, I wrote this:

#!/bin/sh

# Version of readlink that follows links to the end; good for Mac OS X

for file in "$@"; do
  while [ -h "$file" ]; do
    l=`readlink $file`
    case "$l" in
      /*) file="$l";;
      *) file=`dirname "$file"`/"$l"
    esac
  done
  #echo $file
  python -c "import os,sys; print os.path.abspath(sys.argv[1])" "$file"
done
Refrigerator answered 16/3, 2016 at 19:24 Comment(0)
D
0
FILEPATH="file.txt"
echo $(realpath $(dirname $FILEPATH))/$(basename $FILEPATH)

This works even if the file doesn't exist. It does require the directory containing the file to exist.

Droopy answered 14/2, 2018 at 19:4 Comment(1)
GNU Realpath does not require the last element in the path to exist either, unless you use realpath -eCreaky
C
0

I know this is an ancient question. I'm still offering an alternative. Recently I met the same issue and found no existing and portable command to do that. So I wrote the following shell script which includes a function that can do the trick.

#! /bin/sh                                                                                                                                                

function normalize {
  local rc=0
  local ret

  if [ $# -gt 0 ] ; then
    # invalid
    if [ "x`echo $1 | grep -E '^/\.\.'`" != "x" ] ; then
      echo $1
      return -1
    fi

    # convert to absolute path
    if [ "x`echo $1 | grep -E '^\/'`" == "x" ] ; then
      normalize "`pwd`/$1"
      return $?
    fi

    ret=`echo $1 | sed 's;/\.\($\|/\);/;g' | sed 's;/[^/]*[^/.]\+[^/]*/\.\.\($\|/\);/;g'`
  else
    read line
    normalize "$line"
    return $?
  fi

  if [ "x`echo $ret | grep -E '/\.\.?(/|$)'`" != "x" ] ; then
    ret=`normalize "$ret"`
    rc=$?
  fi

  echo "$ret"
  return $rc
}

https://gist.github.com/bestofsong/8830bdf3e5eb9461d27313c3c282868c

Corn answered 10/3, 2018 at 10:49 Comment(0)
R
0

Since none of the presented solutions worked for me, in the case where a file does not exist, I implemented my idea. The solution of André Anjos had the problem that paths beginning with ../../ were resolved wrongly. For example ../../a/b/ became a/b/.

function normalize_rel_path(){
  local path=$1
  result=""
  IFS='/' read -r -a array <<< "$path"
  i=0
  for (( idx=${#array[@]}-1 ; idx>=0 ; idx-- )) ; do
    c="${array[idx]}"
    if [ -z "$c" ] || [[ "$c" == "." ]];
    then
      continue
    fi
    if [[ "$c" == ".." ]]
    then
      i=$((i+1))
    elif [ "$i" -gt "0" ];
    then
      i=$((i-1))
    else
      if [ -z "$result" ];
      then
        result=$c
      else
        result=$c/$result
      fi
    fi
  done
  while [ "$i" -gt "0" ]; do
    i=$((i-1))
    result="../"$result
  done  
  unset IFS
  echo $result
}
Rhynd answered 9/12, 2021 at 14:17 Comment(0)
D
-1

I discovered today that you can use the stat command to resolve paths.

So for a directory like "~/Documents":

You can run this:

stat -f %N ~/Documents

To get the full path:

/Users/me/Documents

For symlinks, you can use the %Y format option:

stat -f %Y example_symlink

Which might return a result like:

/usr/local/sbin/example_symlink

The formatting options might be different on other versions of *NIX but these worked for me on OSX.

Delano answered 30/5, 2014 at 0:58 Comment(1)
the stat -f %N ~/Documents line is a red herring... your shell is replacing ~/Documents with /Users/me/Documents, and stat is just printing its argument verbatim.Kept
S
-4

A simple solution using node.js:

#!/usr/bin/env node
process.stdout.write(require('path').resolve(process.argv[2]));
Supernatural answered 12/1, 2016 at 22:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.