1

I am updating an existing Perl script that uses GetOptions from Getopt::Long. I want to add an option that takes a string as its parameter and can only have one of 3 values: small, medium, or large. Is there any way to make Perl throw an error or kill the script if any other string value is specified? So far I have:

my $value = 'small';
GetOptions('size=s'  => \$value);
miken32
  • 42,008
  • 16
  • 111
  • 154
Samuel
  • 8,063
  • 8
  • 45
  • 41

5 Answers5

3

You could use a subroutine to handle the processing of that option. User-defined subroutines to handle options

my $size = 'small';  # default
GetOptions('size=s'  => \&size);
print "$size\n";

sub size {
    my %sizes = (
            small  => 1,
            medium => 1,
            large  => 1
    );

    if (! exists $sizes{$_[1]}) {
        # die "$_[1] is not a valid size\n";

        # Changing it to use an exit statement works as expected
        print "$_[1] is not a valid size\n";
        exit;
    }

    $size = $_[1];
}

I put the sizes into a hash, but you could use an array and grep as toolic showed.

Ron Bergin
  • 1,070
  • 1
  • 6
  • 7
  • According to the docs it should have died, but you're right, it didn't. Let me run another test. – Ron Bergin Jul 22 '15 at 14:27
  • 2
    From the docs - "If the subroutine needs to signal an error, it should call die() with the desired error message as its argument. GetOptions() will catch the die(), issue the error message, and record that an error result must be returned upon completion." So the module is calling the subroutine in an `eval`. – Dave Cross Jul 22 '15 at 14:32
  • The issue I have with having to call die() twice, which was not real clear to me when I read the docs, is that the error message will then also include "Died at .... line x" which is fine for the developer, but IMO not real clean as an end user message. – Ron Bergin Jul 22 '15 at 14:54
  • But you can make them both into useful end user error messages. The one inside the validation subroutine explains what the actual problem is and the one on the call to `GetOptions` can be `or die "Error in command line options` or something vague like that. – Dave Cross Jul 22 '15 at 15:11
  • Yes, you're right and I should have thought of that especially since I normally couple Getopt::Long with Pod::Usage and do this: `GetOptions('size=s' => \&size) or pod2usage(2);` – Ron Bergin Jul 22 '15 at 15:22
1

One way is to use grep to check if the value is legal:

use warnings;
use strict;
use Getopt::Long;

my $value = 'small';
GetOptions('size=s'  => \$value);

my @legals = qw(small medium large);
die "Error: must specify one of @legals" unless grep { $_ eq $value } @legals;

print "$value\n";
toolic
  • 57,801
  • 17
  • 75
  • 117
1

It's just one of a few checks you need to perform after GetOptions returned.

  • You need to check if GetOptions succeeded.
  • You may need to check the value provided for each optional argument.
  • You may need to check the number of arguments in @ARGV.
  • You may need to check the arguments in @ARGV.

Here's how I perform those checks:

use Getopt::Long qw( );

my %sizes = map { $_ => 1 } qw( small medium large );

my $opt_size;

sub parse_args {
   Getopt::Long::Configure(qw( :posix_default ));

   $opt_size = undef;

   Getopt::Long::GetOptions(
      'help|h|?' => \&exit_with_usage,
      'size=s'   => \$opt_size,
   )
      or exit_bad_usage();

   exit_bad_usage("Invalid size.\n")
      if defined($size) && !$sizes{$size};

   exit_bad_usage("Invalid number of arguments.\n")
      if @ARGV;
}

Here's how I handle failures:

use File::Basename qw( basename );

sub exit_with_usage {
   my $prog = basename($0);
   print("usage: $prog [options]\n");
   print("       $prog --help\n");
   print("\n");
   print("Options:");
   print("   --size {small|medium|large}\n");
   print("      Controls the size of ...\n"
   exit(0);
} 

sub exit_bad_usage {
   my $prog = basename($0);
   warn(@_) if @_;
   die("Use $prog --help for help\n");
   exit(1);
} 
ikegami
  • 367,544
  • 15
  • 269
  • 518
0

This might be overkill, but also take a look Getopt::Again, which implements validation through its process configuration value per command line argument.

use strict;
use warnings;
use Getopt::Again;

opt_add my_opt => (
    type        => 'string',
    default     => 'small',     
    process     => qr/^(?:small|medium|large)$/, 
    description => "My option ...",
);

my (%opts, @args) = opt_parse(@ARGV);
simbabque
  • 53,749
  • 8
  • 73
  • 136
  • I didn't see that Getopt::Long actually has that feature too. Consider this as a TIMTOWTDI alternative. – simbabque Jul 22 '15 at 14:28
0

An alternative to Getopt::Long is Getopt::Declare which has built in pattern support, but is slightly more verbose:

use strict; 
use warnings; 

use feature qw/say/;
use Getopt::Declare; 

my $args = Getopt::Declare->new(
   join "\n",
      '[strict]', 
      "-size <s:/small|medium|large/>\t small, medium, or large [required]"
) or exit(1);

say $args->{-size};

Test runs:

[hmcmillen]$ perl test.pl -size small
small
[hmcmillen]$ perl test.pl -size medium
medium
[hmcmillen]$ perl test.pl -size large
large
[hmcmillen]$ perl test.pl -size extra-large
Error: incorrect specification of '-size' parameter
Error: required parameter -size not found.
Error: unrecognizable argument ('extra-large')

(try 'test.pl -help' for more information)
Hunter McMillen
  • 59,865
  • 24
  • 119
  • 170