[Pdx-pm] mkdir -p ?

Michael G Schwern schwern at pobox.com
Fri Aug 24 14:36:26 PDT 2007


Roderick A. Anderson wrote:
> Keith Lofstrom wrote:
>> The dirvish backup script (written in Perl) does a mkdir to make the
>> directory where new backups go.  One of the users makes many backups
>> per day, and would like to organize those into subtrees.   This
>> would be easy if Perl had the equivalent of "mkdir -p ARG" rather
>> than just "mkdir ARG". 
>>
>> I can think of many ways to fake "mkdir -p" (recursive, system call,
>> etc.) but there is probably an elegant way to do it.  Suggestions?
> 
> Well I loved all the other suggestions.  All _very_ perl but I had the 
> same need and just cheated since it was a Linux system and no need to 
> make it work on Windows.
> 
> 	qx{ /bin/mkdir -p path/that/was/needed }

"That was the best milkshake I've ever had."

(Famous last words)

Instead of saying "no need to make it work on Windows" change that in your
head to "this code will only work on my machine" because that's where that
road leads, and its a short one.

I don't know how many times I've had to dig out shell calls replicating simple
perl functions in code that suddenly does have to be compatible.  It's not a
fun job.  And its not even Windows vs Unix, GNU vs BSD tools alone makes for
some interesting times.  I've taken production code off a Linux machine and
tried to run it on my Macbook (hey its all Unix, right?) only to have to dig
out hard coded GNUisms.  You start making shell calls for mkdir and you're
going to do it for chmod instead of using chmod() or ls instead of readdir()
or rm -rf instead of rmtree and even these "simple" utilities aren't.

Shell calls are loaded with traps.  There are at least six subtle bugs in that
"simple" line of qx code.

1) It's hard coded the location of mkdir
1a) The alternative relies on the user's PATH being set properly
2) You're going to forget to escape special characters in the path
3) You're going to forget to escape spaces in the path
4) It makes assumptions about the shell
5) It makes assumptions about mkdir (does it have -p?)
6) It makes assumptions about the shell argument length limit
7) Failure is not checked

You might laugh at some of these, but I have to maintain MakeMaker, possibly
the most cross-platform pile of shell code in the universe.  Trust me, they're
all real issues.  Shell compatibility sucks.  Perl was written so you don't
have to get involved with that mess.  More importantly, you don't want to get
the next guy working on your code involved.

1-6 are non-issues by using File::Path::mkpath().  The best way to avoid a
mistake is to make it impossible to make that mistake; especially when it also
makes the code smaller and simpler.  Simpler code means #7 becomes much less
of an issue.

Which would you rather work with?

	use File::Path;
	mkpath($path) or die "Can't mkpath $path";

Or

	# Calling system() as a list at least avoids the shell.
	unless( system("/bin/mkdir", "-p", $path) == 0 ) {
            die "Can't mkdir -p $path";
        }

When I must do a system call I wrap it in something like...

	sub run {
		my($program, @args) = @_;
		return 1 if system($program, @args) == 0;
                die "$program @args exited with status ".$? >> 8;
        }

Then at least I only have to worry about getting it right once.

While I'm on the subject, I should mention Shell::Command.  A handy little
module that replicates a small pile of the most common shell utilities for
your convenience.  touch, mv and cp are the biggest.  Patches welcome for more.

And if someone tries to make an optimization argument in either direction
they're getting a tire iron in the face.  Your program is not being slowed
down by making directories.  If it is, something is very wrong or very
specialized.


-- 
Robrt:   People can't win
Schwern: No, but they can riot after the game.


More information about the Pdx-pm-list mailing list