tags:

views:

383

answers:

3

If you have a path to a file (for example, /home/bob/test/foo.txt) where each subdirectory in the path may or may not exist, how can I create the file foo.txt in a way that uses "/home/bob/test/foo.txt" as the only input instead of creating every nonexistent directory in the path one by one and finally creating foo.txt itself?

+10  A: 

You can use File::Basename and File::Path

 use strict;
 use File::Basename;
 use File::Path qw/make_path/;

 my $file = "/home/bob/test/foo.txt";
 my $dir = dirname($file);
 make_path($dir);
 open my $fh, '>', $file or die "Ouch: $!\n"; # now go do stuff w/file

I didn't add any tests to see if the file already exists but that's pretty easy to add with Perl.

seth
I think you mean `mkpath`, not `make_path`? Otherwise, good answer.
hobbs
@Hobbs: check the docs for `File::Path`. `mkpath` is described as "legacy", and the new interface uses `make_path` for that function.
Telemachus
Ah, that's a recent change. The version I have doesn't have that interface. :)
hobbs
+1  A: 

Use make_dir from File::Util

   use File::Util;
   my($f) = File::Util->new();
   $f->make_dir('/var/tmp/tempfiles/foo/bar/');

   # optionally specify a creation bitmask to be used in directory creations
   $f->make_dir('/var/tmp/tempfiles/foo/bar/',0755);
Nifle
+1  A: 

I don't think there's a standard function that can do all of what you ask, directly from the filename.

But mkpath(), from the module File::Path, can almost do it given the filename's directory. From the File::Path docs:

The "mkpath" function provides a convenient way to create directories, even if your "mkdir" kernel call won't create more than one level of directory at a time.

Note that mkpath() does not report errors in a nice way: it dies instead of just returning zero, for some reason.

Given all that, you might do something like:

use File::Basename;
use File::Path;

my $fname = "/home/bob/test/foo.txt";

eval {
    local $SIG{'__DIE__'};  # ignore user-defined die handlers
    mkpath(dirname($fname));
};
my $fh;
if ($@) {
    print STDERR "Error creating dir: $@";
} elsif (!open($fh, ">", $fname)) {
    print STDERR "Error creating file: $!\n";
}
Michael Krebs
The underlying code is around two decades old, and programming fashions have changed a lot since then. You're using the old-style File::Path interface. The new interface allows you to catch all errors and deal with (or ignore) them as appropriate.
dland
Good to know there's a newer interface out there. I'm used to having to write portable Perl that works for old versions and across OSes (hence, the two-arg open()), so I don't usually tease myself by learning what's new in Perl. :-)
Michael Krebs
@Michael: I understand that you might need to write old Perl for whatever reasons, but I don't understand why you would post such code as examples for others. If you're aware of the three-argument form of `open`, please use it around here. We don't need more examples on the net of the two-argument form; we have more than enough already.
Telemachus
@Telemachus: I don't think it's so bad to post portable code. Maybe I could annotate as such (if I'm aware, as I was with open()), but I believe in giving people the tools to write code that'll always work for them and whomever they pass it on to.For example, Nifle's answer using File::Util may work for some, but that module's not on my machine by default. And for the newer File::Path interface I had to find a more recently built Linux box.After some testing it looks like the 3-arg open() works going back pretty far. So you've at least convinced me to use that in future examples.
Michael Krebs