Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

Given an arbitrary single argument representing a file (or directory, device, etc), how do I get the absolute path of the argument?

I've seen many answers to this question involving find/ls/stat/readlink and $PWD, but none that suits my need. It looks like the closest answer is ksh's "whence" command, but I need it to work in sh/bash.

Assume a file, foo.txt, is located in my home directory, /Users/matthew/foo.txt. I need the following behavior, despite what my current working directory is (I'm calling the command "abs"):

(PWD is ~)
$ abs foo.txt
/Users/matthew/foo.txt

$ abs ~/foo.txt
/Users/matthew/foo.txt

$ abs ./foo.txt
/Users/matthew/foo.txt

$ abs /Users/matthew/foo.txt
/Users/matthew/foo.txt

What would "abs" really be?

TIA, Matthew

share|improve this question

migrated from stackoverflow.com Nov 28 '12 at 4:12

This question came from our site for professional and enthusiast programmers.

    
not programming related (superuser, probably) –  KevinDTimm Nov 27 '12 at 20:47
    
have you tried which? I know it's available in many Linux distributions. I guess the answer may depend on what you mean by "Unix". On my LinuxMint box, which bash returns "/bin/bash". –  hall.stephenk Nov 27 '12 at 20:50
1  
And why don't any of the other solutions suit your needs? What's wrong with them? –  larsks Nov 27 '12 at 20:50
    
hall.stephenk: which only works with binaries in $PATH. –  larsks Nov 27 '12 at 20:50
    
@larsks: the other solutions don't work because they don't behave like my fictional "abs" does. –  Matthew Adams Nov 27 '12 at 21:55

2 Answers 2

up vote 4 down vote accepted

On Linux (which uses GNU coreutils), readlink -f does what you need.

For BSD derived systems or OS X, you may need to do some more work. See How can I get the behavior of GNU's readlink -f on a Mac?

share|improve this answer
    
I didn't realize that readlink on BSD-based systems (I'm on OS X) behave differently. That might be the problem. –  Matthew Adams Nov 27 '12 at 21:57
1  
thanks for the pointer. That lead me to a solution, which was at sites.google.com/site/jdisnard/realpath –  Matthew Adams Nov 27 '12 at 22:15

Here's a perl script I wrote years ago that does exactly that. It chases symlinks to get the "canonical" path:

#!/usr/bin/perl -w

use diagnostics;
require Cwd;

$CMD = $0;
$CMD =~ s,^.*/,,;

sub err {
  print STDERR "\n",@_,"\n" if (scalar(@_));
  exit(1);
}

sub warning {
  print STDERR "\n",@_,"\n" if (scalar(@_));
}

sub syntax {
  print STDERR @_,"\n" if (scalar(@_));
  print STDERR "Use -h for help.\n";
  exit(1);
}

sub help {
  print(join("\n",
             ("Usage: $CMD [OPTIONS] file file ..",
              "  $CMD prints the 'real' path for each file by expanding all",
              "  symbolic links and resolving references to '.', '..' and",
              "  extra '/' characters.  The resulting path will have no symbolic",
              "  link, '.' or '..' components.",
              "Options:",
              "  -h      // print this message",
              "  -v      // show process of resolving file",
              "")));
}
sub abspath {
  my $path = shift(@_);
  return ( ($path =~ m,^/,)
           ? $path
           : ( (($#_ > -1) ? shift(@_) : Cwd::getcwd()) . "/" . $path )
         );
}

sub realpath {
  my $left_path = abspath(@_);
  my @left;
  my @right;
  my @link;
  my $link;

  my $upcount = 0;

  @right = ();
  @left = split("/",$left_path);
  shift(@left) if $#left;

  while(@left) {

    $left_path = "/" . join("/",@left);

    printf("test: %s ## %s\n",$left_path,join("/",@right)) if $verbose;

    if($left[$#left] eq "..") {
      $upcount++;
      pop(@left);
    }
    elsif($left[$#left] eq "." || $left[$#left] eq "") {
      # a "/./" or a "//"
      pop(@left);
    }
    elsif ($upcount) {
      return undef unless $#left >= $upcount - 1;
      $#left -= $upcount;
      $upcount = 0;
    }
    else {
      return undef unless  ( -e $left_path );
      if ( $link = readlink($left_path) ) {
        printf("    : %s --> %s\n",$left_path,$link) if $verbose;
        @link = split("/",$link);
        if ($link[0] eq "") {
          @left = @link;
          shift(@left);
        } else {
          pop(@left);
          push(@left,@link);
        }
      } else {
        unshift(@right,pop(@left));
      }
    }
  }
  printf("done: /%s\n",join("/",@right)) if $verbose;
  return("/" . join("/",@right));
}


$verbose = 0;
@files = ();
while ($arg = shift(@ARGV)) {

  if ($arg eq "-h") {
    help();
    exit(9);

  } elsif ($arg eq "-v") {
    $verbose = 1;

  } elsif ($arg eq "--") {
    last;

  } elsif ($arg =~ m/^-/) {
    syntax("Unknown option: '$arg'");

  } else {
    unshift(@ARGV,$arg);
    last;
  }
}

@files = @ARGV;
@ARGV = ();

my $err = 0;
my $f;
my $p;
foreach $f (@files) {
  print "\n" if $verbose;
  $p = realpath($f);
  if(defined($p)) {
    print("$p\n");
  }
  else {
    warning("$f: no such file\n");
    $err++;
  }
}

exit($err);
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.