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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.