2 # Copyright (c) 1996, 1997, 1998 Shigio Yamaguchi. All rights reserved.
3 # This program is free software; you can redistribute it and/or
4 # modify it under the same terms as Perl itself.
9 package File::PathConvert;
16 use vars qw($VERSION @ISA @EXPORT_OK);
19 @EXPORT_OK = qw(setfstype splitpath joinpath splitdirs joindirs realpat
20 abs2rel rel2abs $maxsymlinks $verbose $SL $resolved );
23 use vars qw( $maxsymlinks $verbose $SL $resolved ) ;
27 # Initialize @EXPORT_OK vars
29 $maxsymlinks = 32; # allowed symlink number in a path
30 $verbose = 0; # 1: verbose on, 0: verbose off
31 $SL = '' ; # Separator char export
32 $resolved = '' ; # realpath() intermediate value export
34 #############################################################################
39 my $fstype ; # A name indicating the type of filesystem currently in us
42 my $sepRE ; # RE to match spearator
43 my $notsepRE ; # RE to match anything else
44 my $volumeRE ; # RE to match the volume name
45 my $directoryRE ; # RE to match the directory name
46 my $isrootRE ; # RE to match root path: applied to directory portion only
47 my $thisDir ; # Name of this directory
48 my $thisDirRE ; # Name of this directory
49 my $parentDir ; # Name of parent directory
50 my $parentDirRE ; # RE to match parent dir name
51 my $casesensitive ; # Set to non-zero for case sensitive name comprisions. On
53 # affects names, not any other REs, so $isrootRE for Win32
54 # must be case insensitive
55 my $idempotent ; # Set to non-zero if '//' is equivalent to '/'. This
56 # does not affect leading '//' and '\\' under Win32,
57 # but will fold '///' and '////', etc, in to '//' on this
64 # The following globals are regexs used in the indicated routines. These
65 # are initialized by setfstype, so they don't need to be rebuilt each time
66 # the routine that uses them is called.
68 my $basenamesplitRE ; # Used in realpath() to split filenames.
73 # This RE matches (and saves) the portion of the string that is just before
74 # the beginning of a name
76 my $beginning_of_name ;
79 # This whopper of an RE looks for the pattern "name/.." if it occurs
80 # after the beginning of the string or after the root RE, or after a separator
82 # We don't assume that the isrootRE has a trailing separator.
83 # It also makes sure that we aren't eliminating '../..' and './..' patterns
84 # by using the negative lookahead assertion '(?!' ... ')' construct. It also
85 # ignores 'name/..name'.
87 my $name_sep_parentRE ;
90 # Matches '..$', '../' after a root
91 my $leading_parentRE ;
94 # Matches things like '/(./)+' and '^(./)+'
99 # Matches trailing '/' or '/.'
104 #############################################################################
111 # setfstype: takes the name of an operating system and sets up globals that
112 # allow the other functions to operate on multiple OSs. See
113 # %fsconfig for the sets of settings.
115 # This is run once on module load to configure for the OS named
119 # i) $osname, as in $^O or plain english: "MacOS", "DOS, etc.
120 # This is _not_ usually case sensitive.
121 # r) Name of recognized name on success else undef. Note that, as
122 # shipped, 'unix' is the default is nothing else matches.
123 # go) $fstype and lots of internal parameters and regexs.
124 # x) Dies if a parameter required in @fsconfig is missing.
127 # There are some things I couldn't figure a way to parameterize by setting
128 # globals. $fstype is checked for filesystem type-specific logic, like
129 # VMS directory syntax.
131 # Setting up for a particular OS type takes two steps: identify the OS and
132 # set all of the 'atomic' global variables, then take some of the atomic
133 # globals which are regexps and build composite values from them.
135 # The atomic regexp terms are generally used to build the larger composite
136 # regexps that recognize and break apart paths. This leads to
137 # two important rules for the atomic regexp terms:
139 # (1) Do not use '(' ... ')' in the regex terms, since they are used to build
140 # regexs that use '(' ... ')' to parse paths.
142 # (2) They must be built so that a '?' or other quantifier may be appended.
143 # This generally means using the '(?:' ... ')' or '[' ... ']' to group
144 # multicharacter patterns. Other '(?' ... ')' may also do.
146 # The routines herein strive to preserve the
147 # original separator and root settings, and, it turns out, never need to
148 # prepend root to a string (although they do need to insert separators on
149 # occasion). This is good, since the Win32 root expressions can be like
150 # '/', '\', 'A:/', 'a:/', or even '\\' or '//' for UNC style names.
152 # Note that the default root and default notsep are not used, and so are
155 # For DOS, MacOS, and VMS, we assume that all paths handed in are on the same
156 # volume. This is not a significant limitation except for abs2rel, since the
157 # absolute path is assumed to be on the same volume as the base path.
162 # Find the best match for OS and set up our atomic globals accordingly
163 if ( $osname =~ /^(?:(ms)?(dos|win(32|nt)?))/i )
168 $notsepRE = '[^\\\\/]' ;
169 $volumeRE = '(?:^(?:[a-zA-Z]:|(?:\\\\\\\\|//)[^\\\\/]+[\\\\/][^\
171 $directoryRE = '(?:(?:.*[\\\\/](?:\.\.?$)?)?)' ;
172 $isrootRE = '(?:^[\\\\/])' ;
176 $parentDirRE = '(?:\.\.)' ;
180 elsif ( $osname =~ /^MacOS$/i )
186 $volumeRE = '(?:^(?:.*::)?)' ;
187 $directoryRE = '(?:(?:.*:)?)' ;
188 $isrootRE = '(?:^:)' ;
192 $parentDirRE = '(?:\.\.)' ;
196 elsif ( $osname =~ /^VMS$/i )
201 $notsepRE = '[^\.\]]' ;
202 # volume is node::volume:, where node:: and volume: are optional
203 # and node:: cannot be present without volume. node can include
204 # an access control string in double quotes.
206 # quoted full node names
207 # embedding a double quote in a string ("" to put " in)
208 # support ':' in node names
209 # foreign file specifications
210 # task specifications
211 # UIC Directory format (use the 6 digit name for it, instead)
212 $volumeRE = '(?:^(?:(?:[\w\$-]+(?:"[^"]*")?::)?[\w\$-]+:)?)' ;
213 $directoryRE = '(?:(?:\[.*\])?)' ;
215 # Root is the lack of a leading '.', unless string is empty, which
216 # means 'cwd', which is relative.
217 $isrootRE = '(?:^[^\.])' ;
219 $thisDirRE = '\[\]' ;
225 elsif ( $osname =~ /^URL$/i )
227 # URL spec based on RFC2396 (ftp://ftp.isi.edu/in-notes/rfc2396.txt)
232 # Volume= scheme + authority, both optional
233 $volumeRE = '(?:^(?:[a-zA-Z][a-zA-Z0-9+-.]*:)?(?://[^/?]*)?)' ;
235 # Directories do _not_ include the query component: we pretend that
236 # anything after a "?" is the filename or part of it. So a '/'
237 # terminates and is part of the directory spec, while a '?' or '#'
238 # terminate and are not part of the directory spec.
240 # We pretend that ";param" syntax does not exist
242 $directoryRE = '(?:(?:[^?#]*/(?:\.\.?(?:$|(?=[?#])))?)?)' ;
243 $isrootRE = '(?:^/)' ;
247 $parentDirRE = '(?:\.\.)' ;
248 # Assume case sensitive, since many (most?) are. The user can override
249 # this if they so desire.
260 $directoryRE = '(?:(?:.*/(?:\.\.?$)?)?)' ;
261 $isrootRE = '(?:^/)' ;
265 $parentDirRE = '(?:\.\.)' ;
270 # Now set our composite regexps.
272 # Maintain old name for backward compatibility
275 # Build lots of REs used below, so they don't need to be built every time
276 # the routines that use them are called.
277 $basenamesplitRE = '^(.*)' . $sepRE . '(' . $notsepRE . '*)$' ;
279 $leading_parentRE = '(' . $isrootRE . '?)(?:' . $parentDirRE . $sepRE . ')
280 (?:' . $parentDirRE . '$)?' ;
281 $trailing_sepRE = '(.)' . $sepRE . $thisDirRE . '?$' ;
283 $beginning_of_name = '(?:^|' . $isrootRE . '|' . $sepRE . ')' ;
286 '(' . $beginning_of_name . ')(?:' . $thisDirRE . $sepRE . ')+';
289 '(' . $beginning_of_name . ')'
290 . '(?!(?:' . $thisDirRE . '|' . $parentDirRE . ')' . $sepRE . ')'
292 . $sepRE . $parentDirRE
293 . '(?:' . $sepRE . '|$)'
301 notsepRE = /$notsepRE/
302 volumeRE = /$volumeRE/
303 directoryRE = /$directoryRE/
304 isrootRE = /$isrootRE/
306 thisDirRE = /$thisDirRE/
307 parentDir = "$parentDir"
308 parentDirRE = /$parentDirRE/
309 casesensitive = "$casesensitive"
321 # splitpath: Splits a path into component parts: volume, dirpath, and filename
324 # Very much like File::Basename::fileparse(), but doesn't concern
325 # itself with extensions and knows about volume names.
327 # Returns ($volume, $directory, $filename ).
329 # The contents of the returned list varies by operating system.
333 # $directory: up to, and including, final '/'
334 # $filename: after final '/'
337 # $volume: drive letter and ':', if present
338 # $directory and $filename are like on Unix, but '\' and '/' are
339 # equivalent and the $volume is not in $directory..
342 # $volume: up to and including first ":"
343 # $directory: "[...]" component
344 # $filename: the rest.
348 # $volume: up to ':', then '//stuff/morestuff'. No trailing '/'.
349 # $directory: after $volume, up to last '/'
350 # $filename: the rest.
355 # i) $nofile: if true, then any trailing filename is assumed to
356 # belong to the directory for non-VMS systems.
357 # r) list of ( $volume, $directory, $filename ).
360 my( $path, $nofile )= @_ ;
361 my( $volume, $directory, $file ) ;
362 if ( $fstype ne 'VMS' && $fstype ne 'URL' && $nofile ) {
363 $path =~ m/($volumeRE)(.*)$/ ;
369 $path =~ m/($volumeRE)($directoryRE)(.*)$/ ;
375 # For Win32 UNC, force the directory portion to be non-empty. This is
376 # because all UNC names are absolute, even if there's no trailing separator
377 # after the sharename.
379 # This is a bit of a hack, necesitated by the implementation of $isrootRE,
380 # which is only applied to the directory portion.
382 # A better long term solution might be to make the isroot test a member
383 # function in the future, object-oriented version of this.
386 if ( $fstype eq 'Win32' && $volume =~ /^($sepRE)$sepRE/ && $directory eq
389 return ( $volume, $directory, $file ) ;
394 # joinpath: joins the results of splitpath(). Not really necessary now, but
398 # - Self documenting code
399 # - Future handling of other filesystems
401 # For instance, if you leave the ':' or the '[' and ']' out of VMS $volume
402 # and $directory strings, this patches it up. If you leave out the '['
403 # and provide the ']', or vice versa, it is not cleaned up. This is
404 # because it's useful to automatically insert both '[' and ']', but if you
405 # leave off only one, it's likely that there's a bug elsewhere that needs
408 # Automatically inserts a separator between directory and filename if needed
411 # Automatically inserts a separator between volume and directory or file
412 # if needed for Win32 UNC names.
414 sub joinpath($;$;$;) {
415 my( $volume, $directory, $filename )= @_ ;
417 # Fix up delimiters for $volume and $directory as needed for various OSs
418 if ( $fstype eq 'VMS' ) {
420 if ( $volume ne '' && $volume !~ m/:$/ ) ;
422 $directory = join( '', ( '[', $directory, ']' ) )
423 if ( $directory ne '' && $directory !~ m/^\[.*\]$/ ) ;
426 # Add trailing separator to directory names that require it and
427 # need it. URLs always require it if there are any directory
430 if ( $directory ne ''
431 && ( $fstype eq 'URL' || $filename ne '' )
432 && $directory !~ m/$sepRE$/
435 # Add trailing separator to volume for UNC and HTML volume
436 # names that lack it and need it.
437 # Note that if a URL volume is a scheme only (ends in ':'),
438 # we don't require a separator: it's a relative URL.
440 if ( ( ( $fstype eq 'Win32' && $volume =~ m#^$sepRE{2}# )
441 || ( $fstype eq 'URL' && $volume =~ m#[^:/]$# )
443 && $volume !~ m#$sepRE$#
444 && $directory !~ m#^$sepRE#
445 && ( $directory ne '' || $filename ne '' )
449 return join( '', $volume, $directory, $filename ) ;
454 # splitdirs: Splits a string containing directory portion of a path
455 # in to component parts. Preserves trailing null entries, unlike split().
457 # "a/b" should get you [ 'a', 'b' ]
459 # "a/b/" should get you [ 'a', 'b', '' ]
461 # "/a/b/" should get you [ '', 'a', 'b', '' ]
463 # "a/b" returns the same array as 'a/////b' for those OSs where
464 # the seperator is idempotent (Unix and DOS, at least, but not VMS).
467 # i) directory path string
470 my( $directorypath )= @_ ;
472 $directorypath =~ s/^\[(.*)\]$/$1/
473 if ( $fstype eq 'VMS' ) ;
476 # split() likes to forget about trailing null fields, so here we
477 # check to be sure that there will not be any before handling the
480 return split( $sepRE, $directorypath )
481 if ( $directorypath !~ m/$sepRE$/ ) ;
484 # since there was a trailing separator, add a file name to the end, then
485 # do the split, then replace it with ''.
487 $directorypath.= "file" ;
488 my( @directories )= split( $sepRE, $directorypath ) ;
489 $directories[ $#directories ]= '' ;
491 return @directories ;
495 # joindirs: Joins an array of directory names in to a string, adding
496 # OS-specific delimiters, like '[' and ']' for VMS.
498 # Note that empty strings '' are no different then non-empty strings,
499 # but that undefined strings are skipped by this algorithm.
501 # This is done the hard way to preserve separators that are already
502 # present in any of the directory names.
504 # Could this be made faster by using a join() followed
505 # by s/($sepRE)$sepRE+/$1/g?
508 # i) array of directory names
509 # o) string representation of directory path
514 $directory_path = shift
515 while ( ! defined( $directory_path ) && @_ ) ;
517 if ( ! defined( $directory_path ) ) {
518 $directory_path = '' ;
524 next if ( ! defined( $_ ) ) ;
526 $directory_path .= $sep
527 if ( $directory_path !~ /$sepRE$/ && ! /^$sepRE/ ) ;
529 $directory_path .= $_ ;
533 $directory_path = join( '', '[', $directory_path, ']' )
534 if ( $fstype eq 'VMS' ) ;
536 return $directory_path ;
541 # realpath: returns the canonicalized absolute path name
545 # r) resolved name on success else undef
547 # resolved name on success else the path name which
548 # caused the problem.
551 # Note: this implementation is based 4.4BSD version realpath(3).
553 # TODO: Speed up by using Cwd::abs_path()?
557 my($backdir) = cwd();
558 my($dirname, $basename, $links, $reg);
560 $resolved = regularize($resolved);
564 # Find the dirname and basename.
565 # Change directory to the dirname component.
567 if ($resolved =~ /$sepRE/) {
568 ($dirname, $basename) = $resolved =~ /$basenamesplitRE/ ;
569 $dirname = $sep if ( $dirname eq '' );
570 $resolved = $dirname;
571 unless (chdir($dirname)) {
572 warn("realpath: chdir($dirname) failed: $! (in ${\cwd()}).") i
579 $basename = $resolved;
582 # If it is a symlink, read in the value and loop.
583 # If it is a directory, then change to that directory.
585 if ( $basename ne '' ) {
587 unless ($resolved = readlink($basename)) {
588 warn("realpath: readlink($basename) failed: $! (in ${\cwd(
594 if (++$links > $maxsymlinks) {
595 warn("realpath: too many symbolic links: $links.") if $ver
602 unless (chdir($basename)) {
603 warn("realpath: chdir($basename) failed: $! (in ${\cwd()})
613 # Get the current directory name and append the basename.
616 if ( $basename ne '' ) {
617 $resolved .= $sep if ($resolved ne $sep);
618 $resolved .= $basename
626 # abs2rel: make a relative pathname from an absolute pathname
629 # i) $path absolute path(needed)
630 # i) $base base directory(optional)
631 # r) relative path of $path
633 # Note: abs2rel doesn't check whether the specified path exist or not.
636 my($path, $base) = @_;
639 my( $path_volume, $path_directory, $path_file )= splitpath( $path,'nofile'
641 if ( $path_directory !~ /$isrootRE/ ) {
642 warn("abs2rel: nothing to do: '$path' is relative.") if $verbose;
649 my( $base_volume, $base_directory, $base_file )= splitpath( $base,'nofile'
651 # check for a filename, since the nofile parameter does not work for OSs
652 # like VMS that have explicit delimiters between the dir and file portions
653 warn( "abs2rel: filename '$base_file' passed in \$base" )
654 if ( $base_file ne '' && $verbose ) ;
656 if ( $base_directory !~ /$isrootRE/ ) {
657 # Make $base absolute
658 my( $cw_volume, $cw_directory, $dummy ) = splitpath( cwd(), 'nofile' )
660 # maybe we should warn if $cw_volume ne $base_volume and both are not
662 $base_volume= $cw_volume
663 if ( $base_volume eq '' && $cw_volume ne '' ) ;
664 $base_directory = join( '', $cw_directory, $sep, $base_directory ) ;
667 #print( "[$path_directory,$base_directory]\n" ) ;
668 $path_directory = regularize( $path_directory );
669 $base_directory = regularize( $base_directory );
670 #print( "[$path_directory,$base_directory]\n" ) ;
671 # Now, remove all leading components that are the same, so 'name/a'
672 # 'name/b' become 'a' and 'b'.
673 my @pathchunks = split($sepRE, $path_directory);
674 my @basechunks = split($sepRE, $base_directory);
676 if ( $casesensitive )
678 while (@pathchunks && @basechunks && $pathchunks[0] eq $basechunks[0])
687 && lc( $pathchunks[0] ) eq lc( $basechunks[0] )
695 # No need to use joindirs() here, since we know that the arrays
697 $path_directory= join( $sep, @pathchunks );
698 $base_directory= join( $sep, @basechunks );
699 #print( "[$path_directory,$base_directory]\n" ) ;
701 # Convert $base_directory from absolute to relative
702 if ( $fstype eq 'VMS' ) {
703 $base_directory= $sep . $base_directory
704 if ( $base_directory ne '' ) ;
707 $base_directory=~ s/^$sepRE// ;
710 #print( "[$base_directory]\n" ) ;
711 # $base_directory now contains the directories the resulting relative path
712 + # must ascend out of before it can descend to $path_directory. So,
713 # replace all names with $parentDir
714 $base_directory =~ s/$notsepRE+/$parentDir/g ;
715 #print( "[$base_directory]\n" ) ;
717 # Glue the two together, using a separator if necessary, and preventing an
719 if ( $path_directory ne '' && $base_directory ne '' ) {
720 $path_directory = "$base_directory$sep$path_directory" ;
722 $path_directory = "$base_directory$path_directory" ;
725 $path_directory = regularize( $path_directory ) ;
727 # relative URLs should have no name in the volume, only a scheme.
728 $path_volume=~ s#/.*##
729 if ( $fstype eq 'URL' ) ;
730 return joinpath( $path_volume, $path_directory, $path_file ) ;
734 # rel2abs: make an absolute pathname from a relative pathname
736 # Assumes no trailing file name on $base. Ignores it if present on an OS
740 # i) $path relative path (needed)
741 # i) $base base directory (optional)
742 # r) absolute path of $path
744 # Note: rel2abs doesn't check if the paths exist.
747 my( $path, $base ) = @_;
750 my( $path_volume, $path_directory, $path_file )= splitpath( $path, 'nofile
752 if ( $path_directory =~ /$isrootRE/ ) {
753 warn( "rel2abs: nothing to do: '$path' is absolute" )
758 warn( "rel2abs: volume '$path_volume' passed in relative path: \$path" )
759 if ( $path_volume ne '' && $verbose ) ;
762 if ( !defined( $base ) || $base eq '' ) ;
764 my( $base_volume, $base_directory, $base_file )= splitpath( $base, 'nofile
766 # check for a filename, since the nofile parameter does not work for OSs
767 # like VMS that have explicit delimiters between the dir and file portions
768 warn( "rel2abs: filename '$base_file' passed in \$base" )
769 if ( $base_file ne '' && $verbose ) ;
771 if ( $base_directory !~ /$isrootRE/ ) {
772 # Make $base absolute
773 my( $cw_volume, $cw_directory, $dummy ) = splitpath( cwd(), 'nofile' )
775 # maybe we should warn if $cw_volume ne $base_volume and both are not
777 $base_volume= $cw_volume
778 if ( $base_volume eq '' && $cw_volume ne '' ) ;
779 $base_directory = join( '', $cw_directory, $sep, $base_directory ) ;
782 $path_directory = regularize( $path_directory );
783 $base_directory = regularize( $base_directory );
785 my $result_directory ;
786 # Avoid using a separator if either directory component is empty.
787 if ( $base_directory ne '' && $path_directory ne '' ) {
788 $result_directory= joindirs( $base_directory, $path_directory ) ;
791 $result_directory= "$base_directory$path_directory" ;
794 $result_directory = regularize( $result_directory );
796 return joinpath( $base_volume, $result_directory, $path_file ) ;
802 # Removes dubious and redundant information.
803 # should only be called on directory portion on OSs
804 # with volumes and with delimeters that separate dir names from file names,
805 # since the separators can take on different semantics, like "\\" for UNC
806 # under Win32, or '.' in filenames under VMS.
811 # Combine idempotent separators. Do this first so all other REs only
812 # need to match one separator. Use the first sep found instead of
813 # sepRE to preserve slashes on Win32.
814 $in =~ s/($sepRE)$sepRE+/$1/g
817 # We do this after deleting redundant separators in order to be consistent
819 # If a Win32 path ended in \/, we want to be sure that the \ is returned,
821 $in =~ /($sepRE)$sepRE*$/ ;
822 my $trailing_sep = defined( $1 ) ? $1 : '' ;
824 # Delete all occurences of 'name/..(/|$)'. This is done with a while
825 # loop to get rid of things like 'name1/name2/../..'. We chose the pattern
826 # name/../ as the target instead of /name/.. so as to preserve 'rootness'.
827 while ($in =~ s/$name_sep_parentRE/$1/g) {}
829 # Get rid of ./ in '^./' and '/./'
830 $in =~ s/$dot_sep_etcRE/$1/g ;
832 # Get rid of trailing '/' and '/.' unless it would leave an empty string
833 $in =~ s/$trailing_sepRE/$1/ ;
835 # Get rid of '../' constructs from absolute paths
836 $in =~ s/$leading_parentRE/$1/
837 if ( $in =~ /$isrootRE/ ) ;
839 # # Default to current directory if it's now empty.
840 # $in = $thisDir if $_[0] eq '' ;
842 # Restore trailing separator if it was lost. We do this to preserve
843 # the 'dir-ness' of the path: paths that ended in a separator on entry
844 # should leave with one in case the caller is using trailing slashes to
845 # indicate paths to directories.
847 if ( $trailing_sep ne '' && $in !~ /$sepRE$/ ) ;
858 abs2rel - convert an absolute path to a relative path
860 rel2abs - convert a relative path to an absolute path
862 realpath - convert a logical path to a physical path (resolve symlinks)
864 splitpath - split a path in to volume, directory and filename components
866 joinpath - join volume, directory, and filename components to form a path
868 splitdirs - split directory specification in to component names
870 joindirs - join component names in to a directory specification
872 setfstype - set the file system type
877 use File::PathConvert qw(realpath abs2rel rel2abs setfstype splitpath
878 joinpath splitdirs joindirs $resolved);
880 $relpath = abs2rel($abspath);
881 $abspath = abs2rel($abspath, $base);
883 $abspath = rel2abs($relpath);
884 $abspath = rel2abs($relpath, $base);
886 $path = realpath($logpath) || die "resolution stopped at $resolved";
888 ( $volume, $directory, $filename )= splitpath( $path ) ;
889 ( $volume, $directory, $filename )= splitpath( $path, 'nofile' ) ;
891 $path= joinpath( $volume, $directory, $filename ) ;
893 @directories= splitdirs( $directory ) ;
894 $directory= joindirs( @directories ) ;
898 File::PathConvert provides functions to convert between absolute and
899 relative paths, and from logical paths to physical paths on a variety of
900 filesystems, including the URL 'filesystem'.
902 Paths are decomposed internally in to volume, directory, and, sometimes
903 filename portions as appropriate to the operation and filesystem, then
904 recombined. This preserves the volume and filename portions so that they may
905 be returned, and prevents them from interfering with the path conversions.
907 Here are some examples of path decomposition. A '****' in a column indicates
908 the column is not used in C<abs2rel> and C<rel2abs> functions for that
912 FS VOLUME Directory filename
913 ======= ======================= =============== =============
914 URL http: /a/b/ c?query
915 http://fubar.com /a/b/ c?query
916 //p.d.q.com /a/b/c/ ?query
918 VMS Server::Volume: [a.b] c
919 Server"access spec":: [a.b] c
923 \\server\Volume \a\b\c ****
924 \\server\Volume \a/b/c ****
926 Unix **** \a\b\c ****
928 MacOS Volume:: a:b:c ****
930 Many more examples abound in the test.pl included with this module.
932 Only the VMS and URL filesystems indicate if the last name in a path is a
933 directory or file. For other filesystems, all non-volume names are assumed to
934 be directory names. For URLs, the last name in a path is assumed to be a
935 filename unless it ends in '/', '/.', or '/..'.
937 Other assumptions are made as well, especially MacOS and VMS. THESE MAY CHANGE
938 BASED ON PROGRAMMER FEEDBACK!
940 The conversion routines C<abs2rel>, C<rel2abs>, and C<realpath> are the
941 main focus of this package. C<splitpath> and C<joinpath> are provided to
942 allow volume oriented filesystems (almost anything non-unixian, actually)
943 to be accomodated. C<splitdirs> and C<joindirs> provide directory path
944 grammar parsing and encoding, which is especially useful for VMS.
950 This is called automatically on module load to set the filesystem type
951 according to $^O. The user can call this later set the filesystem type
952 manually. If the name is not recognized, unix defaults are used. Names
953 matching /^URL$/i, /^VMS$/i, /^MacOS$/i, or /^(ms)?(win|dos)/32|nt)?$/i yield
954 the appropriate (hopefully) filesystem settings. These strings may be
955 generalized in the future.
959 File::PathConvert::setfstype( 'url' ) ;
960 File::PathConvert::setfstype( 'Win32' ) ;
961 File::PathConvert::setfstype( 'HAL9000' ) ; # Results in Unix default
965 C<abs2rel> converts an absolute path name to a relative path:
966 converting /1/2/3/a/b/c relative to /1/2/3 returns a/b/c
968 $relpath= abs2rel( $abspath ) ;
969 $relpath= abs2rel( $abspath, $base ) ;
971 If $abspath is already relative, it is returned unchanged. Otherwise the
972 relative path from $base to $abspath is returned. If $base is undefined the
973 current directory is used.
975 The volume and filename portions of $base are ignored if present.
976 If $abspath and $base are on different volumes, the volume from $abspath is
979 No filesystem calls are made except for getting the current working directory
980 if $base is undefined, so symbolic links are not checked for or resolved, and
981 no check is done for existance.
986 'a/b/c' == abs2rel( 'a/b/c', $anything )
987 'a/b/c' == abs2rel( '/1/2/3/a/b/c', '/1/2/3' )
990 'a\\b/c' == abs2rel( 'a\\b/c', $anything )
991 'a\\b/c' == abs2rel( '/1\\2/3/a\\b/c', '/1/2/3' )
994 'http:a/b/c' == abs2rel( 'http:a/b/c', $anything )
995 'http:a/b/c' == abs2rel( 'http:/1/2/3/a/b/c',
996 'ftp://t.org/1/2/3/?z' )
997 'http:a/b/c?q' == abs2rel( 'http:/1/2/3/a/b/c/?q',
998 'ftp://t.org/1/2/3?z' )
999 'http://s.com/a/b/c?q' == abs2rel( 'http://s.com/1/2/3/a/b/c?q',
1000 'ftp://t.org/1/2/3/?z')
1004 C<rel2abs> makes converts a relative path name to an absolute path:
1005 converting a/b/c relative to /1/2/3 returns /1/2/3/a/b/c.
1007 $abspath= rel2abs( $relpath ) ;
1008 $abspath= rel2abs( $relpath, $base ) ;
1010 If $relpath is already absolute, it is returned unchanged. Otherwise $relpath
1011 is taken to be relative to $base and the resulting absolute path is returned.
1012 If $base is not supplied, the current working directory is used.
1014 The volume portion of $relpath is ignored. The filename portion of $base is
1015 also ignored. The volume from $base is returned if present. The filename
1016 portion of $abspath is returned if present.
1018 No filesystem calls are made except for getting the current working directory
1019 if $base is undefined, so symbolic links are not checked for or resolved, and
1020 no check is done for existance.
1022 C<rel2abs> will not return a path of the form "./file".
1027 '/a/b/c' == rel2abs( '/a/b/c', $anything )
1028 '/1/2/3/a/b/c' == rel2abs( 'a/b/c', '/1/2/3' )
1031 '\\a\\b/c' == rel2abs( '\\a\\b/c', $anything )
1032 '/1\\2/3\\a\\b/c' == rel2abs( 'a\\b/c', '/1\\2/3' )
1033 'C:/1\\2/3\\a\\b/c' == rel2abs( 'D:a\\b/c', 'C:/1\\2/3' )
1034 '\\\\s\\v/1\\2/3\\a\\b/c' == rel2abs( 'D:a\\b/c', '\\\\s\\v/1\\2/3' )
1037 'http:/a/b/c?q' == rel2abs( 'http:/a/b/c?q', $anything )
1038 'ftp://t.org/1/2/3/a/b/c?q'== rel2abs( 'http:a/b/c?q',
1039 'ftp://t.org/1/2/3?z' )
1044 C<realpath> makes a canonicalized absolute pathname and
1045 resolves all symbolic links, extra ``/'' characters, and references
1046 to /./ and /../ in the path.
1047 C<realpath> resolves both absolute and relative paths.
1048 It returns the resolved name on success, otherwise it returns undef
1049 and sets the valiable C<$File::PathConvert::resolved> to the pathname
1050 that caused the problem.
1052 All but the last component of the path must exist.
1054 This implementation is based on 4.4BSD realpath(3). It is not tested under
1055 other operating systems at this time.
1057 If '/sys' is a symbolic link to '/usr/src/sys':
1060 '/usr/src/sys/kern' == realpath('../sys/kern');
1061 '/usr/src/sys/kern' == realpath('/sys/kern');
1071 Note that joinpath( splitpath( $path ) ) usually yields path. URLs
1072 with directory components ending in '/.' or '/..' will be fixed
1073 up to end in '/./' and '/../'.
1086 C<realpath> is not fully multiplatform.
1095 In URLs, paths not ending in '/' are split such that the last name in the
1096 path is a filename. This is not intuitive: many people use such URLs for
1097 directories, and most servers send a redirect. This may cause programers
1098 using this package to code in bugs, it may be more pragmatic to always assume
1099 all names are directory names. (Note that the query portion is always part
1104 If the relative and base paths are on different volumes, no error is
1105 returned. A silent, hopefully reasonable assumption is made.
1109 No detection of unix style paths is done when other filesystems are
1110 selected, like File::Basename does.
1116 Barrie Slaymaker <rbs@telerama.com>
1117 Shigio Yamaguchi <shigio@wafu.netgate.net>