14

In Perl 5, I can use Getopt::Long to parse commandline arguments with some validation (see below from http://perldoc.perl.org/Getopt/Long.html).

use Getopt::Long;
my $data   = "file.dat";
my $length = 24;
my $verbose;
GetOptions ("length=i" => \$length,    # numeric
            "file=s"   => \$data,      # string
            "verbose"  => \$verbose)   # flag
or die("Error in command line arguments\n");

say $length;
say $data;
say $verbose;

Here =i in "length=i" creates a numeric type constraint on the value associated with --length and =s in "file=s" creates a similar string type constraint.

How do I do something similar in Raku (née Perl 6)?

Christopher Bottoms
  • 11,218
  • 8
  • 50
  • 99

2 Answers2

20

Basics

That feature is built into Raku (formerly known as Perl 6). Here is the equivalent of your Getopt::Long code in Raku:

sub MAIN ( Str  :$file    = "file.dat"
         , Num  :$length  = Num(24)
         , Bool :$verbose = False
         )
{
    $file.say;
    $length.say;
    $verbose.say;
}

MAIN is a special subroutine that automatically parses command line arguments based on its signature.

Str and Num provide string and numeric type constraints.

Bool makes $verbose a binary flag which is False if absent or if called as --/verbose. (The / in --/foo is a common Unix command line syntax for setting an argument to False).

: prepended to the variables in the subroutine signature makes them named (instead of positional) parameters.

Defaults are provided using $variable = followed by the default value.

Aliases

If you want single character or other aliases, you can use the :f(:$foo) syntax.

sub MAIN ( Str  :f(:$file)    = "file.dat"
         , Num  :l(:$length)  = Num(24)
         , Bool :v(:$verbose) = False
         )
{
    $file.say;
    $length.say;
    $verbose.say;
}

:x(:$smth) makes additional alias for --smth such as short alias -x in this example. Multiple aliases and fully-named is available too, here is an example: :foo(:x(:bar(:y(:$baz)))) will get you --foo, -x, --bar, -y and --baz and if any of them will pass to $baz.

Positional arguments (and example)

MAIN can also be used with positional arguments. For example, here is Guess the number (from Rosetta Code). It defaults to a min of 0 and max of 100, but any min and max number could be entered. Using is copy allows the parameter to be changed within the subroutine:

#!/bin/env perl6
multi MAIN
#= Guessing game (defaults: min=0 and max=100)
{
    MAIN(0, 100)
}

multi MAIN ( $max )
#= Guessing game (min defaults to 0)
{
    MAIN(0, $max)
}

multi MAIN
#= Guessing game
( $min is copy #= minimum of range of numbers to guess
, $max is copy #= maximum of range of numbers to guess
)
{
    #swap min and max if min is lower
    if $min > $max { ($min, $max) = ($max, $min) }

    say "Think of a number between $min and $max and I'll guess it!";
    while $min <= $max {
        my $guess = (($max + $min)/2).floor;
        given lc prompt "My guess is $guess. Is your number higher, lower or equal (or quit)? (h/l/e/q)" {
            when /^e/ { say "I knew it!"; exit }
            when /^h/ { $min = $guess + 1      }
            when /^l/ { $max = $guess          }
            when /^q/ { say "quiting"; exit    }
            default   { say "WHAT!?!?!"        }
        }
    }
    say "How can your number be both higher and lower than $max?!?!?";
}

Usage message

Also, if your command line arguments don't match a MAIN signature, you get a useful usage message, by default. Notice how subroutine and parameter comments starting with #= are smartly incorporated into this usage message:

./guess --help
Usage:
  ./guess -- Guessing game (defaults: min=0 and max=100)
  ./guess <max> -- Guessing game (min defaults to 0)
  ./guess <min> <max> -- Guessing game

    <min>    minimum of range of numbers to guess
    <max>    maximum of range of numbers to guess

Here --help isn't a defined command line parameter, thus triggering this usage message.

See also

See also the 2010, 2014, and 2018 Perl 6 advent calendar posts on MAIN, the post Parsing command line arguments in Perl 6, and the section of Synopsis 6 about MAIN.

Christopher Bottoms
  • 11,218
  • 8
  • 50
  • 99
  • 1
    Thanks to the anonymous editor who explained parameter aliases. Also thanks for introducing me to the idea of placing the commas before the 2nd, 3rd, etc parameters in the signature. I think that this can really help make signatures more tidy. – Christopher Bottoms Sep 25 '17 at 16:30
  • Can the variable's value, e.g. `$file` be assigned inside MAIN, but also carried outside of MAIN? – con May 23 '18 at 13:39
  • @con I assume you are referring to the `Getopt` technique of declaring your variables ahead of time and then populating them using `Getoptions`. I haven't tried that, because I find this technique cleaner. If I need the value of `$file` to be used elsewhere, I can use it as an argument to another subroutine (a.k.a. function). Please explain further, if I seem to be missing your point. – Christopher Bottoms May 23 '18 at 14:33
  • if you want to use the command line variables (e.g. `$file` or `$length`), you can only use them only inside `MAIN`? I have tried assigning them to variables outside `MAIN` but get errors. `my $file = 'blah'; sub MAIN (Str :f(:$file)) { say "in MAIN $file"; } say "outside MAIN \$file is $file";` – con May 23 '18 at 17:11
  • @con Correct. The `$file` inside the `MAIN` subroutine is scoped to that subroutine. – Christopher Bottoms May 23 '18 at 19:14
  • @con (Sorry about formatting). `perl6 example.p6 --file=foo` results in `outside MAIN $file is blah in MAIN foo` – Christopher Bottoms May 23 '18 at 19:14
  • @con I don't get errors when I run `perl6 example.p6 --file=foo` or `perl6 example.p6 -f=foo`. Perhaps you need an `=` sign. (Perl 6 currently requires an `=` sign for named parameters, even for the single letter options.) – Christopher Bottoms May 23 '18 at 19:20
  • Is there any way I could get the variables outside the scope of `MAIN`? Sometimes my scripts can be very long, I don't want to keep everything trapped in `MAIN` – con May 23 '18 at 21:01
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/171696/discussion-between-christopher-bottoms-and-con). – Christopher Bottoms May 24 '18 at 12:36
5

Alternatively, there is a Getopt::Long for perl6 too. Your program works in it with almost no modifications:

use Getopt::Long;
my $data   = "file.dat";
my $length = 24;
my $verbose;
get-options("length=i" => $length,    # numeric
            "file=s"   => $data,      # string
            "verbose"  => $verbose);  # flag

say $length;
say $data;
say $verbose;
Leon Timmermans
  • 30,029
  • 2
  • 61
  • 110