Once upon a time, file and directory manipulation was considered very convenient. Compared to languages like C and Java, which consider I/O as some sort of distasteful act that should best be done behind at least 9 layers of abstraction, its positively enlightened. But once you use something like Ruby's File and Dir objects Perl starts to look a touch out of date.
In Perl, reading a file or directory is a three step process. Safe path manipulation requires the brilliant but cumbersome File::Spec. Even something like deleting a file requires special code to be safe. Want to reliably delete or create a directory? That's another module, File::Path. Copying a file? File::Copy. And so on.
The root of the problem is that Perl represents paths as just strings. And while that's enough to uniquely identify them, its not nearly enough to actually do anything with them. You want an object, files and directories which know how to do it all. Path::Class provides.
Created by Ken Williams, Path::Class is it. Short, convenient constructors, string overloading and providing just about everything you'd want to do with a path. Since its just sugar on top of all the pre-existing and well-built File modules, its extremely robust.
Here is why it is awesome.
Slurp a file.
# Perl open my $fh, "<", $file; my $content = do { local $/; <$fh> }; close $fh;
# Path::Class my $contents = file($file)->slurp;
# Perl opendir my $dh, $dir; for my $thing (grep { $_ ne '.' or $_ ne '..' } readdir $dh) { ... } closedir $dh;
# Path::Class for my $thing (dir($dir)->children) { ... }
# Perl my($vol, $dir, $file) = File::Spec->splitpath($path); my @dirs = File::Spec->splitdir($dir); pop @dirs; my $newpath = File::Spec->catpath($vol, @dirs, $newdir, $newfile);
# Path::Class my $newpath = file($file)->parent->parent->subdir($newdir)->file($newfile);
I could never remember whether it was Path::Class or Class::Path for some reason. Plus there's that "just throw an error" thing and some weird business about manipulating filenames for a foreign operating system which seemed a oddly pervasive in the source. Thus: File::Fu.
Hi Schwern!
Your conditional reads: $_ ne '.' or $_ ne '..' but it is wrong, and should be either $_ ne '.' and $_ ne '..' or alterantively ! ($_ eq '.' or $_ eq '..'). Furthermore, a better, more idiomatic, way would be to use File::Spec->no_upwards.
Re:Conditional for removing "." and ".."
schwern on 2010-01-03T10:47:02
Interesting, I didn't know about no_upwards.
Path::Class may be a nightmare while testing modules under Win32, as it uses a native path separator, which is good sometimes but breaks tests if the author of the tests just compares a Path::Class object stringification with a path separated by forward slashes like this:
my $file = file('foo/bar');
like $file => qr|foo/bar|; # not ok for Win32 as $file becomes "foo\bar" there.
I'd rather recommend Path::Extended (or Path::Extended::Class if you prefer), which has almost the same API but always uses a forward slash as a path separator.
Re:Path::Extended
schwern on 2010-01-05T00:39:47
I think assuming everything on Windows will take Unix style paths is eventually going to bite you in the ass, and fall flat on its face on VMS, but I see why you'd want to normalize paths. It does make life simpler.
Path::Extended contains some great ideas, things like grep(), save() and copy_to(). It would be nice if Path::Extended was a subclass of Path::Class, but I see there's some issues with that.