develooper Front page | perl.perl5.porters | Postings from March 2001

Re: File::Spec->rel2abs bug?

Thread Next
From:
barries
Date:
March 16, 2001 21:22
Subject:
Re: File::Spec->rel2abs bug?
Message ID:
20010317002155.K2288@jester.slaysys.com
On Fri, Mar 16, 2001 at 10:21:01AM -0600, Stephen R. Wilcoxon wrote:
> Shigio Yamaguchi asked that I send this to you.
> 
> > rel2abs claims it "Converts a relative path to an absolute path" yet it 
> > does not seem to.  File::Spec->rel2abs('../other') run from /root/home and 
> > File::Spec->rel2abs('../other', '/root/home') both result in 
> > /root/home/../other which is definitely not an absolute path.  Was this 
> > behavior intentional?

Actually it is an absolute path: anything beginning in '/' is an
absolute path.  It's not a canonicalized path, which is what I think you
might be wanting, nor is it rationalized according to the local
filesystem, as you would get with realpath (which both canonicalizes and
rationalizes).

See Cwd::realpath(), aka Cwd::abs_path() (I feel the latter is a bit
misnamed, but that's a nit.  It's well documented and pretty clear) for
one that reads the file system and returns the canonical physical path.

Note that even the physical path is a shaky concept in the face of being
able to mount volumes at multiple points on your local file hierarchy in
various OSs, though that rarely matters in practice.

> > Also, are there any plans to have it support the ~user construct?

That's perhaps too Unix specific to go into a cross-platform library,
but it would be very useful if you want to submit a patch that adds
something like File::Spec::Unix->look_up_user_home_dir().  I'm Ccing
perl5-porters to see if others want to weigh in.

> I submitted a bug report and patch yesterday using perlbug.  Here's the 
> patch again.  This follows the general "does not check file system" 
> attitude in File::Spec, but a friend pointed out that the method below for 
> compressing '..' will break on symlinks.  I can't see a way around this 
> without actually using the file system.

My personal take is to leave it as it is, separate from the filesystem.

I've had some code kicking around for a while for a mincanonpath that
does .. reduction.  It's a bit tricky even on a simple path grammar like
Unix's, let alone on filesystems/OSs that support UNC names or other
concepts of volumes (ie multiple roots).  It would make apps a bit more
stable in the face of symlinks, here's the Unix flavor of it:

   sub mincanonpath {
      my $path = canonpath( @_ ) ;

       1 while ( 
	   $path =~ s{
	       (/|^)
	       (?:[^./]|\.[^./]|\.\.[^/])
	       [^/]*/\.\.
	       (?:/|$)
	   }{$1}gx
       ) ;

       $path =~ s@^/(\.\./)*\.\.(?:/|$)@/@;           # /../../xx -> xx
       $path =~ s@/$@@ unless $path eq "/";           # xx/       -> xx
       return $path ;
   }

I have some worked up for MacOS, etc., too and a test suite, but there's
been little call for this.  I should work up a patch and send it in...

> Possibly add a new version of 
> rel2abs and/or canonpath that compress '..' (either the way I have it which 
> breaks on symlinks or with actual checks of the file system).  This patches 
> File::Spec::Unix.

Thanks for the patch, one problem is that this has a high probability of
breaking code somewhere, somehow.  I originally proposed an optional
second arg to canonpath() to turn on this behavior, but that's a
performance hit, and Gurusamy Sarathy suggested mincanonpath() as a new
name for the new functionality.

> 
> 
> *** Unix.pm_old	Thu Mar 15 16:12:49 2001
> --- Unix.pm	Thu Mar 15 16:18:32 2001
> ***************
> *** 26,32 ****
>   =item canonpath
>   
>   No physical check on the filesystem, but a logical cleanup of a
> ! path. On UNIX eliminated successive slashes and successive "/.".
>   
>       $cpath = File::Spec->canonpath( $path ) ;
>   
> --- 26,33 ----
>   =item canonpath
>   
>   No physical check on the filesystem, but a logical cleanup of a
> ! path. On UNIX eliminated successive slashes and successive "/."
> ! and compressed "something/..".
>   
>       $cpath = File::Spec->canonpath( $path ) ;
>   
> ***************
> *** 34,44 ****
>   
>   sub canonpath {
>       my ($self,$path) = @_;
> !     $path =~ s|/+|/|g unless($^O eq 'cygwin');     # xx////xx  -> xx/xx
>       $path =~ s|(/\.)+/|/|g;                        # xx/././xx -> xx/xx
>       $path =~ s|^(\./)+||s unless $path eq "./";    # ./xx      -> xx
> !     $path =~ s|^/(\.\./)+|/|s;                     # /../../xx -> xx
> !     $path =~ s|/\Z(?!\n)|| unless $path eq "/";          # xx/       -> xx
>       return $path;
>   }
>   
> --- 35,65 ----
>   
>   sub canonpath {
>       my ($self,$path) = @_;
> !     $path =~ s|//+|/|g unless($^O eq 'cygwin');    # xx////xx  -> xx/xx
>       $path =~ s|(/\.)+/|/|g;                        # xx/././xx -> xx/xx
>       $path =~ s|^(\./)+||s unless $path eq "./";    # ./xx      -> xx
> !     $path =~ s|/\.$|/|s;                           # xx/.      -> xx/
> !     $path =~ s|^/(\.\./)+|/|s;                     # /../../xx -> /xx
> !     $path = '/' if ($path eq '/..');               # /..       -> /
> !     $path =~ s|/\Z(?!\n)|| unless $path eq "/";    # xx/       -> xx
> ! 
> !     if ($path =~ m|/\.\.|) {                       # xx/yy/..  -> xx
> !         my @parr = split m|/+|, $path;
> !         my $i = 1;
> !         while ($i < @parr) {
> !             if ($parr[$i] eq '..' and $parr[$i-1]) {
> !                 splice @parr, $i-1, 2;
> !                 $i-- unless ($i == 1);
> !             }
> !             else {
> !                 $i++;
> !             }
> !         }
> !         # Need to handle cygwin differently???
> !         $path = join '/', @parr;
> !         $path =~ s|^/(\.\./)+|/|s;                 # /../../xx -> /xx
> !     }
> ! 
>       return $path;
>   }
>   
> ***************
> *** 343,348 ****
> --- 364,384 ----
>   
>   =cut
>   
> + sub _get_homedir {
> +     my($user) = @_;
> +     if (!defined $user or $user eq '') {
> +         eval {
> +             local $SIG{__DIE__};
> +             (getpwuid($<))[7];
> +         } || $ENV{HOME} || undef; # chdir undef changes to home directory, too
> +     } else {
> +         eval {
> +             local $SIG{__DIE__};
> +             (getpwnam($user))[7];
> +         };
> +     }
> + }
> + 
>   sub abs2rel {
>       my($self,$path,$base) = @_;
>   
> ***************
> *** 439,446 ****
>               $base = $self->canonpath( $base ) ;
>           }
>   
> !         # Glom them together
> !         $path = $self->catdir( $base, $path ) ;
>       }
>   
>       return $self->canonpath( $path ) ;
> --- 475,489 ----
>               $base = $self->canonpath( $base ) ;
>           }
>   
> !         if ($path =~ m|^~([^/]*)|) {
> !             my $user = $1;
> !             my $home = _get_homedir($user);
> !             $path =~ s|^~$user|$home|;
> !         }
> !         else {
> !             # Glom them together
> !             $path = $self->catdir( $base, $path ) ;
> !         }
>       }
>   
>       return $self->canonpath( $path ) ;
> 
> 

Thread Next


nntp.perl.org: Perl Programming lists via nntp and http.
Comments to Ask Bjørn Hansen at ask@perl.org | Group listing | About