views:

3925

answers:

6

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

Is there a bash command which does this?

+6  A: 

Try realpath. Bonus: it's available as a bash command and in the standard linux C libraries.


Update: realpath is not part of the standard distribution; we'd been using it for so long that I didn't think to check! 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);
}
Adam Liss
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)
Tim Whitcomb
I haven't seen this command on any system I've worked with.
Jay Conrod
http://www.gnu.org/software/coreutils/ for readlink, but realpath comes from this package: http://packages.debian.org/unstable/utils/realpath
Kent Fredric
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.
Adam Liss
+11  A: 

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.

Tim Whitcomb
This will normalize but not resolve soft links. This may be either a bug or a feature. :-)
Adam Liss
@Adam see man realpath: realpath -s does the same :)
Kent Fredric
Good point. I've usually appreciated it as a feature, but this is something to keep in mind.
Tim Whitcomb
@Kent: thanks for the tip!
Adam Liss
mjs
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?
Tim Whitcomb
+13  A: 

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

Edit

Realpath appears not to be standard issue.

The closest you can get with the stock standard is

readlink -f  /path/here/..

Realpath appears to come from debian, and is not part of coreutils: http://packages.debian.org/unstable/utils/realpath Which was originally part of the DWWW package.

( also available on gentoo as app-admin/realpath )

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

Works the same as

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

in that it doesn't need the path to actually exist to normalise it.

Kent Fredric
+1 for `readlink` - `realpath` isn't avilable on Ubuntu 9.10.
Grundlefleck
A: 

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`
alhernau
A: 

Use readlink.

MY_PATH=$(readlink -f "$0")
mattalexx
A: 

Perhaps this works as well, and is portable:

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