Search code examples
perlcommand-line-argumentsgetoptgetopt-long

How to pass both mandatory and optional command line arguments to perl script?


I am using Getopt::Long to pass options to my Perl script.

But I want to do something like this :

perl myScript mandatoryArgument1 -optionalArgument1=someValue

I want the script to throw an error if mandatoryArgument1 is missing. How can this be done?


Solution

  • The good Getopt::Long does not have a mechanism for that. It specifically processes options.

    However, as it does its work it removes those options from @ARGV so once it's finished you can check whether the expected arguments are there. See the second part for this but I would like to first suggest another way: Make those arguments named and then Getopt will process them.

    Then it is easy to check whether they were submitted. For example

    use warnings;
    use strict;
    use feature 'say';
    use Getopt::Long;
    
    my $mandatoryArg;
    my $opt;
    
    # Read command-line arguments, exit with usage message in case of error
    GetOptions( 'name=s' => \$mandatoryArg, 'flag' => \$opt )
        or usage(); 
    
    if (not defined $mandatoryArg) {
        say STDERR "Argument 'name' is mandatory";
        usage();
    }
    
    # The program goes now. Value for $opt may or may have not been supplied
    
    sub usage {
        say STDERR "Usage: $0 ...";   # full usage message
        exit;
    }
    

    So if --name string isn't given on the command line the $mandatoryArg stays undefined and the program exits. That variable doesn't need a default value since it is mandatory, and it shouldn't have one for this check to work.

    Argument checking and processing is often far more involved, and this is when Getopt shines.


    The mandatoryArgument1 in the question is supplied without a name. While Getopt can be made to act on a non-option input, it cannot detect that an expected one is not there.

    The module does allow to mix arguments with named options, anywhere on the command line. See Option with other arguments in docs. So you can invoke the program as

    script.pl --opt1 value1 unnamed_arg --opt2 value2
    

    but I'd suggest to the user to supply them after named options.

    Then, after GetOptions does its job, @ARGV will contain the string unnamed_arg and you can get it (or find out that it isn't there). Processing of named options by GetOptions is the same as above.

    my ($var1, $var2, $flag);
    
    GetOptions('opt1=s' => \$var1, 'opt2=i' => \$var2, 'f' => \$flag)
        or usage(); 
    
    # All supplied named options have been collected, all else left in @ARGV
    # Read the remaining argument(s) from @ARGV, or exit with message
    
    # This can get far more complicated if more than one is expected
    my $mandatoryArg1 = shift @ARGV || do {
        say STDERR "Mandatory argument (description) is missing";
        usage();
    };
    

    Above you have to process @ARGV by hand once Getopt picked up the named arguments.

    If there is more than one such argument the user has to strictly respect their expected relative position on the command line, as there is in general no way for the program to tell what's what. So errors where the user confuses their order on the command line in general cannot be caught.

    This becomes a hindrance and I'd suggest to have at most one kind of unnamed argument(s), and only in the case where it is obvious what that must be, like filename(s).

    While all this is possible modules like Getopt exist precisely so that we don't have to do it.


      Action for input that doesn't look like an option is set up using the "name" of '<>'

    Getoptions( 'opt=s' => \$var, ..., '<>' => \&arg_cb );
    
    sub arg_cb { say "Doesn't look like an option: $_[0]" }
    

    where the sub arg_cb is invoked only if a non-option-looking argument is seen.