0

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

5
  • 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
5

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 https://stackoverflow.com/questions/1055671/how-can-i-get-the-behavior-of-gnus-readlink-f-on-a-mac

2
  • 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
1

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);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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