Cleaning up a directory name (Removing ".." and "." from directory name)
I am writing an SFTP module using a Java class (Yes. I know it's stupid. Yes, I know about Net::SFTP. It's political why we have to do it this way).
The underlying Java program has basically a few classes to get, put, list, and remove the file from the server. In these calls, you have to give it a directory and file. There is no way to move outside of your original directory. You're stuck doing the tracking yourself.
I decided it would be nice if I kept track of your remote directory, and created a Chdir Method that tracks the directory you're in from the root of the FTP. All I do is store the directory inside an attribute and use it in the other commands. Very simple and it works.
The problem is that the stored directory name gets longer and longer. For example, if the directory is foo/bar/barfoo
, and you do $ftp->Chdir("../..")
, your new directory would be foo/bar/barfoo/../..
and not foo
. Both are technically correct, but the first is cleaner and easier to understand.
I would like some code that will allow me to simplify the directory name. I thought about using File::Spec::canonpath
, but that specifically says it does not do this. It refered me to Cwd
, but that depends upon direct access to the machine, and I'm connecting via FTP.
I've come up with the following code snippet, but it really lacks elegance. It should be simpler to do, and more obvious what it is doing:
use strict;
use warnings;
开发者_开发问答my $directory = "../foo/./bar/./bar/../foo/barbar/foo/barfoo/../../fubar/barfoo/..";
print "Directory = $directory\n";
$directory =~ s{(^|[^.])\.\/}{$1}g;
print "Directory = $directory\n";
while ($directory =~ s{[^/]+/\.\.(/|$)}{}) {
print "Directory = $directory\n";
}
$directory =~ s{/$}{};
print "Directory = $directory\n";
Any idea? I'd like to avoid having to install CPAN modules. They can be extremely difficult to install on our server.
If I were writing this, I would split the directory string on /
and iterate over each piece. Maintaining a stack of pieces, a ..
entry means "pop", .
means do nothing, and anything else means push that string onto the stack. When you are done, just join the stack with /
as the delimiter.
my @parts = ();
foreach my $part (File::Spec->splitdir($directory)) {
if ($part eq '..') {
# Note that if there are no directory parts, this will effectively
# swallow any excess ".." components.
pop(@parts);
} elsif ($part ne '.') {
push(@parts, $part);
}
}
my $simplifiedDirectory = (@parts == 0) ? '.' : File::Spec->catdir(@parts);
If you want to keep leading ..
entries, you will have to do something like this instead:
my @parts = ();
my @leadingdots = ();
foreach my $part (File::Spec->splitdir($directory)) {
if ($part eq '..') {
if (@parts == 0) {
push(@leadingdots, '..');
} else {
pop(@parts);
}
} elsif ($part ne '.') {
push(@parts, $part);
}
}
my $simplifiedDirectory = File::Spec->catdir((@leadingdots, @parts));
I have a pure Perl module on CPAN for trimming paths: Path::Trim. Download, copy and use it from your working directory. Should be easy.
I am not sure if you can access that directory.
If you can, you can go to that directory and do a getcwd
there:
my $temp = getcwd; # save the current directory
system ("cd $directory"); # change to $directory
$directory = getcwd;
system ("cd $temp"); # switch back to the original directory
The SFTP protocol supports the realpath command that does just what you want.
精彩评论