Camelia

Parsing command line arguments in Perl 6 - ARGS - ARGV - MAIN

When writing a program, one of the best ways to make it reusable is by accepting parameters on the command line.

For example if you are writing a program that needs to parse a log file, you might want to supply the name of the logfile on the command line.

If the application needs to send an e-mail report, it is more generic if the user can supply the address as a command line parameter: --to boss@company

How can you let your Perl 6 application accept command line arguments?

Note! This site is about Perl 6.
If you are looking for a solution for Perl 5, please check out the Perl 5 tutorial.

When Perl 6 is launched, it fills the system variable @*ARGS with all the values from the command line. A simple script like the following can demonstrate what we get:

examples/cli/args.pl6

use v6;

say @*ARGS.perl;

Save as args.pl6 and run

perl6 args.pl6 first --second "third and fourth"

The output will be

["first", "--second", "third and fourth"]

The @*ARGS in Perl 6 is the same as @ARGV in Perl 5 and similar to sys.argv in Python. It does NOT contain the name of the executable itself as it is done in C and Python for example.

We could now write some code that goes over the values in @*ARGS, but luckily Perl 6 provides a built-in mechanism for that. It is the same mechanism that works for Perl 6 subroutines. After all the command line parameters of a program are very similar to the arguments of a function.

MAIN - positional parameters

There is a special name in Perl 6 called MAIN. If your main program file contains a subroutine called MAIN, that subroutine will be executed when the program is launched.

Furthermore, the signature of that subroutine is the expected list of arguments of the whole program.

Let's see a simple example:

examples/cli/source.pl6

use v6;

sub MAIN($source) {
    say "source: $source";
}

Save it as source.pl6 and run it as perl6 source.pl6.

You will get:

Usage:
  source.pl6 <source>

That's because the MAIN subroutine expects a single positional argument that will be assigned to the $source variable.

Now run perl6 source.pl6 input.txt.

The output will be:

source: input.txt

That means, the command line parameter was accepted and the MAIN sub was called with $source having the value "input.txt".

However if you run perl6 source.pl6 --name or perl6 source.pl6 -n, or perl6 source.pl6 one two you'll get the usage report:

Usage:
  source.pl6 <source>

More than one positional

We can, of course, expect more than one positional values:

examples/cli/positionals.pl6

use v6;

sub MAIN($source, $target, $count, $debug) {
    say "source: $source";
    say "target: $target";
    say "count:  $count";
    say "debug:  $debug";
}

If we run it without any parameter perl6 positionals.pl6 or without the expected number of (4) parameters: perl6 positionals.pl6 one two or with too many parameters: perl6 positionals.pl6 one two three 4 5 we will see the usage message:

Usage:
  positionals.pl6 <source> <target> <count> <debug>

We must use the correct number of parameters:

$ perl6 positionals.pl6 egy ketto 3 yes
source: egy
target: ketto
count:  3
debug:  yes

Named parameters

Positional parameters are usually not very useful as the user must supply them in the expected order and even if some of them are marked optional by the developer, the user cannot leave out the 2nd parameter if you'd like to supply the 3rd. A much better approach is to expect and supply named parameters.

Just as in regular subroutines, Perl 6 allows us to turn arguments into named parameter in the MAIN sub as well.

We just need to put a colon : in front of the variable name to turn it into a named variable:

examples/cli/named.pl6

use v6;

sub MAIN(:$source, :$target, :$count, :$debug) {
    say "source: $source";
    say "target: $target";
    say "count:  $count";
    say "debug:  $debug";
}

If we run perl6 named.pl6 We get lots of warnings:

Use of uninitialized value $source of type Any in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
  in sub MAIN at files/examples/cli/named.pl6 line 4
source:
Use of uninitialized value $target of type Any in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
  in sub MAIN at files/examples/cli/named.pl6 line 5
target:
Use of uninitialized value $count of type Any in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
  in sub MAIN at files/examples/cli/named.pl6 line 6
count:
Use of uninitialized value $debug of type Any in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
  in sub MAIN at files/examples/cli/named.pl6 line 7
debug:

That happens because the named parameters are, by default, optional but their value is not initialized. So if we blindly print them out we will get these warnings.

There are several ways to fix this.

Check if arguments are defined?

One would be to check if the values are defined before printing them:

examples/cli/named_defined.pl6

use v6;

sub MAIN(:$source, :$target, :$count, :$debug) {
    say "source: $source" if $source.defined;
    say "target: $target" if $target.defined;
    say "count:  $count"  if $count.defined;
    say "debug:  $debug"  if $debug.defined;
}

This allows us to run: perl6 named_defined.pl6 that would print nothing, We could run perl6 named_defined.pl6 --source=hello --debug=yes that would print

source: hello
debug:  yes

And perl6 named_defined.pl6 --help or perl6 named_defined.pl6 --qqrq or any other incorrect name that would print the usage string:

Usage:
  named_defined.pl6 [--source=<Any>] [--target=<Any>] [--count=<Any>] [--debug=<Any>]

Set default values

The other, probably much better way, is to set default values to the attributes:

examples/cli/named_defaults.pl6

use v6;

sub MAIN(:$source, :$target, :$count = 3, :$debug = False) {
    say "source: $source";
    say "target: $target";
    say "count:  $count";
    say "debug:  $debug";
}

In this case we set default values to the debug and count parameters and hope that the user will always supply the source and target.

The normal operation would look like this:

perl6 named_defaults.pl6 --source=here --target=there

source: here
target: there
count:  3
debug:  False

but one could override both the debug and the count parameters: perl6 named_defaults.pl6 --source=here --target=there --debug=no --count=17:

source: here
target: there
count:  17
debug:  no

Empty defaults

In rare cases you might want to set the default values to the empty string or the number zero:

sub MAIN(:$source = '', :$count = 0) { } 

Required parameters

Exclamation point

By default each named parameter is optional, but we could also tell Perl 6, that some of the parameters are required by adding a trailing exclamation point !. For example here we required that both $source and $target will be supplied by the user.

perl6 named_required.pl6 --source=here --target=there --debug=no --count=1

source: here
target: there
count:  1
debug:  no

If one or more of the required parameters are missing like $target in this example: perl6 named_required.pl6 --source=here --debug=no --count=1

Then we get the usage string:

Usage:
  files/examples/cli/named_required.pl6 --source=<Any> --target=<Any> [--count=<Any>] [--debug=<Any>]

Type restrictions

Perl 6 supports what is called gradual typing which means that while we can declare variables without any type restrictions, we can, as we understand more about our application, add type declarations to variables and let Perl 6 enforce the type checking. This can be also used to enhance the command line parameter processing.

You could declare that the $count variable should be an integer Int or that the $debug variable must be a boolean Bool.

examples/cli/typed.pl6

use v6;

sub MAIN(Str :$source!, Str :$target!, Int :$count = 3, Bool :$debug = False) {
    say "source: $source";
    say "target: $target";
    say "count:  $count";
    say "debug:  $debug";
}

In this case the usage message will indicate the required data type: perl6 typed.pl6

Usage:
  typed.pl6 --source=<Str> --target=<Str> [--count=<Int>] [--debug]

If we supply an invalid value, for examples a string to the $count parameter we get the usage string: perl6 typed.pl6 --source=here --target=there --count=abc So by declaring that a parameter is of type Int we added parameter checking as well.

An even more interesting aspect is when we set a parameter to be Bool. Then suddenly we are not expected to supply a value to the name. The mere presence of the flag will flip the default False to True:

perl6 typed.pl6 --source=here --target=there --count=7 --debug

source: here
target: there
count:  7
debug:  True

Though if we really wanted to, we could still supply the value of the $debug flag:

perl6 typed.pl6 --source=here --target=there --count=7 --debug=False

source: here
target: there
count:  7
debug:  False

Any number of parameters

Finally, there are many cases when after a number of named parameters we would like to accept an arbitrary number of extra values. These are usually filenames we want to act upon, but not necessarily. In order to accept arbitrary number of values, we can declare a slurpy array as one of the parameters:

examples/cli/slurp.pl6

use v6;

sub MAIN(*@files) {
    say "files: " ~ @files.perl;
}

All the values from the command line will be slurped in to that array.

perl6 slurp.pl6 one two.txt "name with space.doc"

files: ["one", "two.txt", "name with space.doc"]

If no parameter is supplied it is still ok. We get back an empty array:

perl6 slurp.pl6

files: []

Named params and slurpy param

In many cases we will need to accept a few named parameters and then an arbitrary number of extra values. We can do that by mixing named parameters and a slurpy array:

examples/cli/named_and_slurpy.pl6

use v6;

sub MAIN(Str :$source!, Str :$target!, Int :$count = 3, Bool :$debug = False, *@files) {
    say "source: $source";
    say "target: $target";
    say "count:  $count";
    say "debug:  $debug";
    say "files: " ~ @files.perl;
}

$ perl6 named_and_slurpy.pl6

Usage:
  named_and_slurpy.pl6 --source=<Str> --target=<Str> [--count=<Int>] [--debug] [<files> ...]

perl6 named_and_slurpy.pl6 --source=from --target=to one two.txt "3 4 5"

source: from
target: to
count:  3
debug:  False
files: ["one", "two.txt", "3 4 5"]

Conclusion

There is more of course, for example we could defined the MAIN sub as a multi and then we can have multiple definitions, but this is enough for now. This can already get you started writing Perl 6 applications that accept parameters on the command line.


The Perl 6 Tricks and Treats newsletter has been around for a while. If you are interested to get special notification when there is new content on this site, it is the best way to keep track:
Email:
Full name:
This is a newsletter temporarily running on my personal site (szabgab.com) using Mailman, till I implement an alternative system in Perl 6.
Gabor Szabo
Written by Gabor Szabo

Published on 2017-07-04



Comments

In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.
comments powered by Disqus
Suggest a change
Elapsed time: 2.387979

Perl 6 Tricks and Treats newsletter

Register to the free newsletter now, and get updates and news.
Email:
Name: