You mentioned that you tried File::Spec
and it didn't do what you want. That's because you were probably using it on a Unix-like system, where if you try to cd
to something like path/to/file.txt/..
it will fail unless path/to/file.txt
is a legitimate directory path.
However, the command cd path/to/file.txt/..
will work on a Win32 system, provided that path/to
is a real directory path -- regardless of whether file.txt
is a real subdirectory.
In case you don't see where I'm going yet, it's that the File::Spec
module won't do what you want (unless you're on a Win32 system), but the module File::Spec::Win32
will do what you want. And what's cool is, File::Spec::Win32
should be available as a standard module even on non-Win32 platforms!
This code pretty much does what you want:
use strict;
use warnings;
use feature 'say';
use File::Spec::Win32;
my $path = '/a/../b/./c//d';
my $canonpath = File::Spec::Win32->canonpath($path);
say $canonpath; # This prints: \b\c\d
Unfortunately, since we're using the Win32 flavor of File::Spec
, the \
is used as the directory separator (instead of the Unix /
). It should be trivial for you to convert those \
to /
, provided that the original $path
does not contain any \
to begin with.
And if your original $path
does contain legitimate \
characters, it shouldn't be too difficult to figure out a way to preserve them (so that they don't get converted to /
). Although I have to say that if your paths actually contain \
characters, they have probably caused quite a bit of headaches so far.
And since Unix-like systems (including Win32) supposedly don't allow for null characters in their pathnames, one solution to preserving the \
characters in your pathnames is to first convert them to null bytes, then call File::Spec::Win32->canonpath( ... );
, and then convert the null bytes back to the \
characters. This can be done very straight-forward, with no looping:
use File::Spec::Win32;
my $path = '/a/../b/./c//d';
$path =~ s[\\][\0]g; # Converts backslashes to null bytes.
$path = File::Spec::Win32->canonpath($path);
$path =~ s[\\][/]g; # Converts \ to / characters.
$path =~ s[\0][\\]g; # Converts null bytes back to backslashes.
# $path is now set to: /b/c/d