How to delete specific directories using Getopt::Long in PERL

Viewed 157

I'm trying to delete a few directories using Getopt::Long. What I want to do is run the script in a directory and delete only the directories (and all the files) I specified (target_1, target_2, target_3). I want to execute it like that -

cleanup_script.pl -target target_1,target_2,target_3

Here is what I wrote so far -

#!/usr/bin/env perl
use strict;
use warnings;
use diagnostics;

#Logs
open(STDOUT, '>', 'results.log') or die "Can't open log";
open(STDERR, '>', 'errors.log') or die "Can't open log";

#Input
use Getopt::Long;
my $target = "target";
(my $target_1, my $target_2, my $target_3) = split ',', $target;
GetOptions ("target=s" => \$target);

#Check and delete
if ($target_1 =~ '/target_1'){
    system("rm -rf $target_1")};
if ($target_2 =~ '/target_2'){
    system("rm -rf $target_2")};
if ($target_3 =~ '/target_3'){
    system("rm -rf $target_3")};

errors.log -

Use of uninitialized value $target_1 in pattern match (m//) at
        cleanup_script.pl line 22 (#1)
    (W uninitialized) An undefined value was used as if it were already
    defined.  It was interpreted as a "" or a 0, but maybe it was a mistake.
    To suppress this warning assign a defined value to your variables.
    
    To help you figure out what was undefined, perl will try to tell you the
    name of the variable (if any) that was undefined. In some cases it cannot
    do this, so it also tells you what operation you used the undefined value
    in.  Note, however, that perl optimizes your program and the operation
    displayed in the warning may not necessarily appear literally in your
    program.  For example, "that $foo" is usually optimized into "that "
    . $foo, and the warning will refer to the concatenation (.) operator,
    even though there is no . in your program.
    
Use of uninitialized value $target_3 in pattern match (m//) at
        cleanup_script.pl line 26 (#1)

Thanks!

3 Answers

The Getopt::Long has an express purpose of parsing the program's arguments and reading them into the program, and into variables. (It can also execute arbitrary code but it would be a grossly bad practice to use that for anything other than for argument-processing needs.)

So once the library has been used to read the string like dir1,dir2,... , which is the value for the argument with your chosen name of target†, then you split that string and obtain the needed list. Then the program can proceed to process (remove submitted directories, in this case)

use Getopt::Long;
use File::Path qw(remove_tree);   # better use this than system's 'rm'

... 

my $target;  # only need to declare

# - I strongly advise an argument with a more informative name, like
#   GetOptions ("dirs-to-remove=s" => \$target);
# - Don't want to proceed with a program that deletes stuff if there is an error!
#   So exit with a brief usage message (or call a function with a nicer message)
GetOptions ("target=s" => \$target)
    or die "Usage: $0 [arguments...]\n";  # or call a sub for usage message

# Check these directories if you have any way to do so      
my @dirs_to_remove = split /,/, $target;

# Now iterate over all those `@dirs_to_remove`
foreach my $dir_to_del (@dirs_to_remove) {
    #
    # Check as best as you can. A whole hierarchy is getting blown away
    #
    if ( -d $dir_to_del and ... ) {   # check somehow
        my $num_removed = remove_tree(
            $dir_to_del, { error => \my $err, safe => 1 }
        );
        if ($err and @$err) {
            # See module docs for how to work with $err
            die "Errors while removing $dir_to_del ..."; 
        }
        # print, check ...; see docs for options
    }
}

Please see the documentation. In particular, remove_tree can take some very useful options. I find the error option particularly good to have as I'd rather exit on any error right away in a program that removes entries.

The library can be invoked on multiple targets, so you can say

my $num_removed = remove_tree( @dirs_to_remove, ... );

In this case you don't need a loop, just this one statement. However, then there is no way to check each directory as it is about to be removed, or after it has been -- they are all just gone.

I recommend always using well established Perl libraries over going out to the system and using external tools, when there are libraries for the job. In this case there sure are, like File::Path.


† Once this program is invoked a bunch of directories will disappear, with possibly no way to recover their content. I get very nervous thinking about that.

So why not name that command-line argument with something screamingly obvious, like

program_name --dirs-to-remove dir1,dir2...

(even if the current name of the program itself is informative and specific!)

Then, you can invoke it more simply, perhaps even as program_name -d dir1,dir2..., since Getopt::Long allows arguments to be shortened as long they stay unambiguous. (But I wouldn't -- I'd rather make myself type that string and in the process re-think what's just about to happen.)

Your has problems that make it unclear. (For example, the regex patterns won't match in the example when one presumes they should match.) You present an error and broken code, but not what the code is trying to achieve.

As such, I'll start with the basic form of the program.

use Getopt::Long;

GetOptions("target=s" => \$target)
   or die("usage");

my @targets = split /,/, $target // "target";

for my $target (@targets) {
   system("rm", "-rf", "--", $target);
}

To that you can add validation.

my %valid_targets = map { $_ => 1 } qw( target1 target2 target3 );

die("...") if !$valid_targets{$target};
system("rm", "-rf", "--", $target);

You could even map keywords to directories.

my %targets = (
   target_1 => ".../foo",
   target_2 => ".../bar",
   target_3 => ".../baz",
);

my $dir = $targets{$target}
   or die("...");

system("rm", "-rf", "--", $dir);

One thing that looks strange is this:

(my $target_1, my $target_2, my $target_3) = split ',', $target;
GetOptions ("target=s" => \$target);

You want $target to be the option that the user typed in from the command line, so that you can get the targets from it, but you do the split BEFORE you call GetOptions to read the value of the -target argument. That doesn't sound right to me.

Also, you shouldn't give $target an initial value.

Related