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, 2012 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, 2012 at 20:50
  • 1
    And why don't any of the other solutions suit your needs? What's wrong with them?
    – larsks
    Nov 27, 2012 at 20:50
  • hall.stephenk: which only works with binaries in $PATH.
    – larsks
    Nov 27, 2012 at 20:50
  • @larsks: the other solutions don't work because they don't behave like my fictional "abs" does.
    – Matthew Adams
    Nov 27, 2012 at 21:55

2 Answers 2

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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .