DISCLAIMER: THESE PAGES ARE STILL UNDER CONSTRUCTION. NO CODE EXAMPLE BEEN TESTED YET.

Perl - Doing it Better

Avoiding backticking `pwd`, `find .`, and `mkdir -p`


[Previous Page] |[Next Page] Table of Contents: small | med | large

I generally don't like backticks anymore. They are good for a beginner to get started, and they are a quick fix. But in general, I think they are a bad thing now.

I think the most common uses of backticks that I've seen are:

Nowadays, I find these to be sloppy practice. In the standard libraries, you are now provided with better ways to do these.

My main complaint is that these are not necessarily portable across platforms, and for that matter might not even be portable across machines across the same platform. Also, you may needlessly be opening shells.


Get current working dir/Avoiding `pwd` - use Cwd [back to top]

The really quick way to get the current directory is:

$currDir = `pwd`;
chomp $currDir;
but I think the better way to do it now is:
  1 #!/bin/sh
  2 #! -*- perl -*-
  3 eval 'exec $PERLLOCATION/bin/perl -x $0 ${1+"$@"} ;'
  4  if 0;
  5 
  6 use Cwd;
  7 $currWorkDir = &Cwd::cwd();

Listing 4.1.1 for code_untested/useCwd.pl

It's still pretty simple, and it's more portable.


Get all of the files/subdirs in the current dir/Avoiding `find .` - use File::Find [back to top]

Start again with the quickie sloppy way:

@allFiles = `find .`;
chomp @allFiles;

This one is actually directly in "Programming Perl" [WALL00] in the modules section for File::Find. I also wind up refering to Perl in a Nutshell [SIEV99] a lot when I work with the standard modules.

  1 #!/bin/sh
  2 #! -*- perl -*-
  3 eval 'exec $PERLLOCATION/bin/perl -x $0 ${1+"$@"} ;'
  4  if 0;
  5 
  6 use File::Find;
  7 @allFiles = ();
  8 &File::Find::find( sub {push @allFiles, $_;}, ".");

Listing 4.1.2 for code_untested/useFileFind.pl

Okay, you might not be too happy about that one. The idea here is that you get the list of files in allFiles, and then after this, you will be doing something to this list of files.

Just a not-so-interesting side note: Our use of @allFiles in this subroutine is technically called a "closure". Even though allFiles is not truly within the scope of the anonymous sub, we're using the anonymous sub to affect its value. Since the sub is defined within the same scope that @allFiles is defined in, the version of allFiles taken inside the anonymous sub is actually the one we want.

EX 4.1.1: designating a final version

Anyway, this is not really the way the Find::file::find routine should be used. Instead of building a list first, and then operating on it, you should operate within the subroutine. Suppose we wanted to do the following:

Look at all the files in the current directory and change anything with the basename of "elementTake57a_inertia" into the basename of "final".

By the way, I think is bad practice to take out the version number from a file name, but just about every production is going to do this... One way you may do this is:

  1 #!/bin/sh
  2 #! -*- perl -*-
  3 eval 'exec $PERLLOCATION/bin/perl -x $0 ${1+"$@"} ;'
  4  if 0;
  5 
  6 use File::Find;
  7 
  8 # run this program and give it the arg elementTake57a_inertia
  9 $finalBasename = $ARGV[0];
 10 @allFiles = ();
 11 &File::Find::find( sub {push @allFiles, $File::Find::name;}, ".");
 12 foreach my $file (@allFiles) {
 13 	my($dir, $basename, $frame, $ext) =
 14 		$file =~ /(.*)\/	# directory up to last slash
 15 			([^\/]*)	# basename - non-slash chars
 16 			\.		# literal dot
 17 			(\d+)		# frame number - digits
 18 			\.(\w+)$/x;	# extention - ends w/ letters+numbers
 19 	if ($basename eq $finalBasename) {
 20 		rename($file, ($dir . '/final.' . $frame . '.' . $ext));
 21 	}
 22 
 23 }

Listing 4.1.3 for code_untested/designateFinal-1.pl

Not my proudest moment of coding, and there are a ton of things wrong. But I'm just going to address the 'find' issue. The above code could be rewritten to completely skip the "allFiles" array:

  1 #!/bin/sh
  2 #! -*- perl -*-
  3 eval 'exec $PERLLOCATION/bin/perl -x $0 ${1+"$@"} ;'
  4  if 0;
  5 
  6 use File::Find;
  7 
  8 # Run this program and give it the arg elementTake57a_inertia
  9 $finalBasename = $ARGV[0];
 10 &File::Find::find(
 11     sub {
 12 	my($basename, $frame, $ext) = $_ =~
 13 			/([^\/]*)	# basename - non-slash chars
 14 			\.		# literal dot
 15 			(\d+)		# frame number - digits
 16 			\.(\w+)$/x;	# extention - ends w/ letters+numbers
 17 	my $dir = $File::Find::dir;
 18 	if ($basename eq $finalBasename) {
 19 		rename($File::Find::name,
 20 				($dir . '/final.' . $frame . '.' . $ext));
 21 	}
 22     }, ".");

Listing 4.1.4 for code_untested/designateFinal.pl

File::Find is very cool module. It's a very good replacement for find, and it's actually very powerful. I generally don't like the imposition of coding styles, but this is actually pretty good.

Actually, in this case, I would make it a little more general, so that it is a general renaming script.

EX 4.1.2: renaming files

  1 #!/bin/sh
  2 #! -*- perl -*-
  3 eval 'exec $PERLLOCATION/bin/perl -x $0 ${1+"$@"} ;'
  4  if 0;
  5 
  6 use File::Find;
  7 
  8 # Run this program and give it the args elementTake57a_inertia final
  9 
 10 ($oldBasename, $newBasename) = @ARGV;
 11 &File::Find::find(
 12     sub {
 13 	my($basename, $frame, $ext) = $_ =~
 14 			/([^\/]*)	# basename - non-slash chars
 15 			\.		# literal dot
 16 			(\d+)		# frame number - digits
 17 			\.(\w+)$/x;	# extention - ends w/ letters+numbers
 18 	my $dir = $File::Find::dir;
 19 	if ($basename eq $oldBasename) {
 20 		rename($File::Find::name,
 21 			($dir . '/' .$newBaseName.'.'.$frame . '.' . $ext));
 22 	}
 23     }, ".");

Listing 4.1.5 for code_untested/newBaseName.pl


Creating a new subdirectory / Avoiding `mkdir -p` - use File::Path [back to top]

Let's start with the sloppy way again. Supposing you're about to run MTOR and you set the display name to "take001/myBasename." Now, one of the things you need to be aware of in MTOR is that it won't create directories for you. That is, you will initially get "rib," "rmantex," "rmanpix," and a couple others. But with the display name of "take001/myBasename," it will expect to drop RIBs into the directory "rib/take001/" with the basename "myBasename." However, you still need to create that directory yourself, or MTOR will just die.

$newDir = 'rib/take001';
system "mkdir -p $newDir";

You probably want a better way to get the $newDir, assembling it out of some other pieces. But the real thing we're addressing here is the mkdir.

  1 #!/bin/sh
  2 #! -*- perl -*-
  3 eval 'exec $PERLLOCATION/bin/perl -x $0 ${1+"$@"} ;'
  4  if 0;
  5 
  6 use Cwd;
  7 use File::Path;
  8 
  9 $currWorkDir = &Cwd::cwd();
 10 $newDir = 'rib/take001';
 11 $fullPath = $currWorkDir . '/' . $newDir;
 12 &File::Path::mkpath($fullPath);

Listing 4.1.6 for code_untested/useFilePath.pl

I can't remember if you are required to have a full path or not, so I just created one to be safe. But this is a good, safe way of creating a path in a portable way.


© 2001 Steve Hwan, hostname: @pacbell.net, username: svhwan
You should probably use the word "PERL" in the subject line to get my attention.
Last Modified: Sun Dec 2 18:59:20 2001