zzspectrez has asked for the wisdom of the Perl Monks concerning the following question:

Fellow Monks,
I have a simple question. What is the best way to handle creating directories which may possibly have parent directories that do not exist.I thought that mkdir would make any parent directories.

However, apparently it does not. It returns the error: No such file or directory. if any of the parent directories do not exist. I have a script Im working on that creates quite a few directories that may have parent directories that do not exist.

My solution is the following. Straining my brain to remember that pascal class I took in junior college, I seem to remember that this could be a good case for recursion. Of course Im no programmer by trade, so this may be ineficient. I dont know! Comments! Suggestions! What do you do??

#!/usr/bin/perl -w use strict; sub my_mkdir { my $path = shift; my $perms = shift; my ($parent) = $path =~ /(.*)\//; if ($parent) { my_mkdir($parent,$perms); mkdir ($path, $perms) or return; return 1; }else { #at base of path mkdir ($path, $perms) or return; return 1; } } my $path = shift; my $perms = 0777; my_mkdir($path, $perms) or die "ERROR: $!\n";

THANKS!
zzSPECTREz

Replies are listed 'Best First'.
(tye)Re: recursive mkdir
by tye (Sage) on Jan 08, 2001 at 10:10 UTC
    use File::Path qw( mkpath ); mkpath( $path, 0, 0777 );

    And, no, I can't see this as a good case for using recursion. Sure, you could code it recursively, but the value of recursion is when it makes coding it easier. I think it would almost be a stretch to do this one recursively so you'd just be working hard to make your code slower. (:

    FYI, see (tye)Re: Recursion for my thoughts on recursion.

    This node has been updated.

            - tye (but my friends call me "Tye")

      Thanks for the suggestion. I hadnt come across that module. Ill have to check it out, Im interested how the module does it.

      I used recursion, because it was the only obvious way I could think of doing it. One question. How much slower could the code be when dealing with creating subdirectories? Since realisticly you are not going to create directories of say 100 deep. The script Im using it in seems to work quickly.

      THANKS!
      zzSPECTREz

        Yeah, sorry, I was just noticing that you had coded it recursively. I didn't notice the recursion and had interpretted your posting as, in part, asking how you would go about rewriting it recursively (to learn about recursion). :-}

        No, doing this recursively isn't going to make this unacceptably slow. I think the reason that you ended up with a recursive solution is that your first idea at how to loop through the list of directories went in the "wrong" order (or you chose this order because you wanted to be optimistic and assume that most of the time you don't need to create many directories so start from that end).

        Recursion is one way to reverse a loop, but there is usually a "better" way. In fact, in Perl you can use the built-in "stacks" to reverse even the most hard to reverse loop via:

        my @stack; while( <> ) { push @stack, $_; } while( @stack ) { local( $_ )= pop @stack; # your code here }
        And it is hard to argue that the above is worse than recursion in any way. ;)

                - tye (but my friends call me "Tye")
Re: recursive mkdir
by davorg (Chancellor) on Jan 08, 2001 at 13:44 UTC

    If you're running on a Unix-like system, then you could always use

    system("mkdir -p $dir");

    which will create all intermediate directories.

    --
    <http://www.dave.org.uk>

    "Perl makes the fun jobs fun
    and the boring jobs bearable" - me

      on many systems the command "mkdirhier" will create subdirectories as well.

      of course, the recursive perl solution *is* much cooler. ;)

      -Nakano

Re: recursive mkdir
by repson (Chaplain) on Jan 08, 2001 at 10:51 UTC
    I'm not suggesting you continue writing the sub yourself, but here's a way (probably not the best way) to write the solution without using recursion (which should be saved for when really needed).
    sub my_mkdir { my $path = shift; my $perms = shift; my @parts = split /\//, $path; for my $num (1..$#parts) { my $check = join('/', @parts[0..$num]); unless (-d $check) { mkdir( $check, $perms ); } } }

      Neat. You can get rid of the join by using a .=.

      sub my_mkdir { my ($path, $perms) = @_; my $dir = '.'; for ( split /\//, $path ) { mkdir $dir .= "/$_", $perms; } }
      That's a nice iterative solution. One small correction: the for loop should start at 0, rather than 1, because it may need to create the very first directory in the path.

      (I tested it with my_mkdir('dir1/dir2/dir3', '0777'); it worked only if dir1 already existed.)

Re: recursive mkdir
by chipmunk (Parson) on Jan 08, 2001 at 10:10 UTC
    I think the best way would be the mkpath() function from File::Path.
Re: recursive mkdir
by a (Friar) on Jan 08, 2001 at 10:32 UTC
    Those other monk's responses notwithstanding (see tye's recursion link for an in-depth discussion) if you did want to go this route (as a learning experience or something) your's is not so bad a stab at it. You might want to look up merlyn's discussions of '.*' (perhaps
    my ($parent) = $path =~ /([^\/]*)\//;
    ?) and don't you want to check the internal return of my_mkdir()? Hmm, maybe not, save for debug/error msging.

    Yoiks! I'm completely backwards here - You want to be greedy. Dang recursion!Forget I mentioned it,... look, out that window, why it's Supermunk! (slinks quietly away to chop onions).

    a

      Heh. The .* is correct here. The first call to my_mkdir matches all the parent directories, and each subsequent recursive call matches one fewer parent directory.

      I believe Im dealing with the return's properly. I want my routine to respond just like mkdir. Return 1 if succesfful and 0 if error and the error from mkdir still in $!.

      Correct me if I got it wrong. Seems to test out right

      zzspectrez
Re: recursive mkdir
by Anonymous Monk on Feb 07, 2014 at 05:40 UTC
    I have written this script to create directory within directory and have tested it.Its working properly and giving desired output. for($i=1;$i<=10;$i++) { $a = $a."file$i"; $x = `mkdir $a`; $a = $a."/"; $c = `cd $x`; print "$c\n"; #$b = $a.$b } By Pragya Yadav ; Atish Pradhan and Pramod Kumar
Re: recursive mkdir
by cherio (Novice) on Dec 30, 2020 at 22:30 UTC
    Well, who needs another subroutine but here it is anyway. It will create all parents except those sitting on top of the file system root "/"
    sub dir_ensure { for (@_) { # handles multiple DIRs my $path = ($_ // die("Dir must be specified")) =~ s!/$!!sr; next if -d $path; # DIR is already there die("Path '$path' already exists and it's not a DIR") if -e _; my $parent = $path =~ m"^(/[^/].*?)/(?!\.{1,2}$)[^/]+$"s ? $1 : die("Invalid path '$path'"); dir_ensure($parent); # ensure parent DIR exists mkdir($path) or die("Failed to create DIR $path $!"); } }
      sub dir_ensure { for (@_) { # handles multiple DIRs my $path = ($_ // die("Dir must be specified")) =~ s!/$!!sr; next if -d $path; # DIR is already there die("Path '$path' already exists and it's not a DIR") if -e _; my $parent = $path =~ m"^(/[^/].*?)/(?!\.{1,2}$)[^/]+$"s ? $1 : die("Invalid path '$path'"); dir_ensure($parent); # ensure parent DIR exists mkdir($path) or die("Failed to create DIR $path $!"); } }

      Problems with that code:

      • Recursion is not needed to split a path into a series of directories, as explained in other posts in this thread.
      • The code assumes a single root directory. There are several operating systems that have more than one root directory. (DOS, Windows, OS/2, ...)
      • There is a race condition: You stat() a part of the path way before you mkdir that part (see TOCTTOU). To avoid that problem, just call mkdir() and check its result. Expect EEXISTS for already existing directories.
      • There is at least one module that already takes care of all those issues, plus OS-specific surprises: File::Path

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)