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

Hey everybody. I've been working on this program and I seem to have hit a bit of a problem. I'm stumped. Maybe I'm just overlooking something or whatever but I cannot figure it out. I want it to run at the command line and change filenames in a specified directory using some search and replace patterns. I want it to support 3 command line flags: -q, -i, --force to help modify the behavior. I know something's not working right, but I must have missed it, anybody have any advice?

Here's what I have:

$cmdline = shift @ARGV; foreach (@ARGV) { $cmdline = $cmdline . " " . $_; } $old = $cmdline; $cmdline =~ s/\b-i\b//; if ($cmdline ne $old) { $old = $cmdline; $interactive = 1; } else { $interactive = 0; } $cmdline =~ s/\b--force\b//; if ($cmdline ne $old) { $old = $cmdline; $force = 1; } else { $force = 0; } $cmdline =~ s/\b-q\b//; if ($cmdline ne $old) { $old = $cmdline; $quiet = 1; } else { $quiet = 0; } if ( $interactive = 1) { $quiet = 0; } print "\"$cmdline\"\n"; $search = $ARGV[0]; #search pattern $replace = $ARGV[1]; #replacement pattern $dir = $ARGV[2]; #directory opendir DH, $dir || die "error opening directory, $dir:\n$!\n"; @filez = readdir(DH); chdir $dir || die "error changing to directory, $dir:\n$!\n"; foreach (@filez) { if (-f $_) { $old = $_; s/($search)/$replace/; if ($quiet != 1) { print "$old $_"; } if ($old ne $_) { if ($force == 1) { if ($interactive == 1) { print " y/n? "; chomp($y = <STDIN>); if ($y =~ /^[yY]/) { rename $old, $_; } } else { rename $old, $_; print "\n"; } } } else { print "\n"; } } }

Replies are listed 'Best First'.
Re: Problem with command-line option parsing
by Enlil (Parson) on Jan 08, 2003 at 04:35 UTC
    Since you are going to use command line options why not use the GetOpt::Long module?

    -enlil

      Or even plain ol' Getopt::Std if you just need simple options.

      -- vek --

        ...please, please, use one of those. (I mean Getopt::Std or Getopt::Long) You will be able to pare your code down by so many lines it's amazing. The latter module allows you more flexibility and power at the price of a steeper learning curve, the former will give you rapid access to a simple means of cleanly parsing your command-line. (But you may have to sacrifice the --force in favor of a -f option.)

        ...All the world looks like -well- all the world, when your hammer is Perl.
        ---v

Re: Problem with command-line option parsing
by pfaut (Priest) on Jan 08, 2003 at 04:27 UTC

    It would help if you could tell us what it's not doing that you think it should be doing but I think the following will clean up the first half of your code.

    sub usage { print "usage: proggie [-q] [-i] [-force] search replace directory\ +n"; exit 2; } my @args; my ($interactive,$quiet,$force); foreach (@ARGV) { $interactive++, next if ($_ eq '-i'); $quiet++, next if ($_ eq '-b'); $force++, next if ($_ eq '-force'); usage() if /^-/; push @args,$_; } usage() if @args != 3; ($search,$replace,$dir) = @args;
    --- print map { my ($m)=1<<hex($_)&11?' ':''; $m.=substr('AHJPacehklnorstu',hex($_),1) } split //,'2fde0abe76c36c914586c';
Re: Hey everybody
by graff (Chancellor) on Jan 08, 2003 at 05:31 UTC
    pfaut gave a nice treatment for the first half, so let me take a stab at the second:

    The way your code is written, the "interactive" flag is only checked when the "force" flag has been set, which seems odd at first glance -- is this what you intended?

    ... if ($force == 1) { if ($interactive == 1) { ...
    If so, then specifying "-i" and "-q" together could get a user into trouble -- he'd be asked to confirm a rename operation without being shown what's going to happen...

    Also, I wonder why you print an empty line on every iteration even when a rename is not called for (that last "else" block). Maybe the code you want for the loop over files is something more like this:

    foreach $old ( grep { -f $_ } @filez ) { $new = $old; next unless ( $new =~ s/$search/$replace/ ); # s/// returns false if there's no match if (( $force and $interactive ) or not $quiet ) { print "$old -> $new"; if ( $interactive ) { print " y/n? "; $yn = <STDIN> # no need for chomp here next unless ( $yn =~ /^y/i ); } else { print "\n" } } rename $old, $new if ( $force ); }
    I haven't tested it entirely, but I think it preserves the intended(?) logic of the original.

    You may want to think more about what makes sense as a set of command line options, e.g. "--verbose" instead of "-q", "-n" (no-op) instead of "--force"; and maybe you want to add to pfaut's suggested ARGV handling, to check for (and die on) incompatible combinations of options -- and make sure you have a "$Usage" string that you print out when essential args are missing or unusable args are given.

    For the heck of it, you might want to check out the "shloop" (shell loop) utility that I have linked from my home node; it supports the use of perl regexes for file renaming, as well as for other command-line operations (copy, delete, symlink, whatever). BTW, that reminds me -- you may need to use quotemeta on $search (or \Q$search\E in the substitution), in case the command line arg contains regex meta-characters (like "+", "$", etc).

Re: Problem with command-line option parsing
by rob_au (Abbot) on Jan 08, 2003 at 11:10 UTC
    Previous nodes in this thread have already addressed the question at hand recommending the usage of the Getopt modules, Getopt::Std and Getopt::Long, for the parsing of command line options. However, upon reading the root node of this thread, I thought I could have a bit of fun in posting my own reply ...

    Firstly let me state, that I do not recommend the use of any of the techniques discussed below in a production environment - Please, please, please follow some of the more sane advice given in the thread above.

    That having been said, I offer the following obfuscated solution which performs all of the tasks of the previously posted code - Please note that this code is not strict compliant, uses no if-else-unless control structures and is generally evil by all measures, for therein lies the fun ...

    #!/usr/bin/perl -s opendir+D,pop;$.=pop;$,=pop;for(grep{-f $_}readdir(D)){$;=$_;s=$,=$.=;(!defined $q||defined$i)&&print"$; $_";(defined$i ) && do{print" y/n?";$y = <STDIN>;($y=~ /^y/i)&&++$force;}; (!defined$q)&&print "\n";defined$force&&rename$;,$_}

    The code snippet above makes use of the -s switch to the perl executable which enables rudimentary parsing of command line arguments passed to a script - This argument enables rudimentary switch parsing on the command line after the script name but before any filename arguments (or before a --). Any switch found is removed from @ARGV and a variable with a corresponding name is set.

    For a discussion about this and other alternate forms of command line argument parsing, see Poor's man command line arguments.

    With this rudimentary form of command line parsing, if the command line arguments -force, -i and -q are passed to this script, the corresponding variables $force, $i and $q are set - These variables are used to control the behaviour of the above script (without the use of any if, unless or while control statements).

    How does this script work? Well ...

    opendir+D,pop; $.=pop; $,=pop;

    After this parsing of command line arguments, this code pop's the directory passed to this script from @ARGV and then does the same for the replacement and search patterns, storing these values in the input record separator, $., and the output record separator, $,, respectively.

    for(grep{-f$_}readdir(D)){ ... }

    The list of files in this directory, as determined by grep-ing through the list of elements returned by readdir and testing each with the -f (file) file test, is looped through - It is in this loop that the majority of work of this script is performed:

    $;=$_; s=$,=$.=;

    The original file name is stored in the variable used as the subscript separator for multidimensional array emulation, $;, while the default variable, $_, is modified by the search and replace pattern, using the values previously stored in the input and output record separators. The = character is used in place of the / character for the separating of the search and replacement pattern in the substitution function.

    (!defined$q||defined$i)&&print"$; $_";

    The old and new filenames are printed if either the quiet command line argument (-q) is not specified or the interactive command line argument (-i) is specified.

    (defined$i)&&do{ .. }

    If the interactive command line argument (-i) is defined, a block containing code to prompt for user input is executed. The code which is executed is the following:

    print" y/n?";$y=<STDIN>;($y=~/^y/i)&&++$force;

    This code prints the text " y/n?", prompts for user input and if the reply starts with the letter 'y', increment the $force variable. This variable has been used so that only a single rename function is required for renaming the file, irrelevant of whether this is specified by user input in interactive form or by specification of the -force command line argument (see the discussion on rudimentary command line argument parsing using the -s argument to the perl executable).

    (!defined$q)&&print"\n";

    Print a trailing new line if the quiet command line argument (-q) is not specified.

    defined$force&&rename$;,$_

    Finally, the file is renamed if the $force variable is set, either within the user interactive mode described above or by the -force command line argument.

    Yes, I am evil ... :-)

     

    perl -le 'print+unpack("N",pack("B32","00000000000000000000001000010010"))'