In Command line interface§

See primary documentation in context for sub MAIN

The sub with the special name MAIN will be executed after all relevant entry phasers (BEGIN, CHECK, INIT, PRE, ENTER) have been run and the mainline of the script has been executed. No error will occur if there is no MAIN sub: your script will then just have to do the work, such as argument parsing, in the mainline of the script.

Any normal exit from the MAIN sub will result in an exit code of 0, indicating success. Any return value of the MAIN sub will be ignored. If an exception is thrown that is not handled inside the MAIN sub, then the exit code will be 1. If the dispatch to MAIN failed, a usage message will be displayed on STDERR and the exit code will be 2.

The command line parameters are present in the @*ARGS dynamic variable and may be altered in the mainline of the script before the MAIN unit is called.

The signature of (the candidates of the multi) sub MAIN determines which candidate will actually be called using the standard multi dispatch semantics.

A simple example:

# inside file 'hello.raku'
sub MAIN($name) {
    say "Hello $name, how are you?"
}

If you call that script without any parameters, you get the following usage message:

$ raku hello.raku
Usage:
  hello.raku <name>

However, if you give a default value for the parameter, running the script either with or without specifying a name will always work:

# inside file 'hello.raku'
sub MAIN($name = 'bashful') {
    say "Hello $name, how are you?"
}
$ raku hello.raku
Hello bashful, how are you?

$ raku hello.raku Liz
Hello Liz, how are you?

Another way to do this is to make sub MAIN a multi:

# inside file 'hello.raku'
multi MAIN()      { say "Hello bashful, how are you?" }
multi MAIN($name) { say "Hello $name, how are you?"   }

Which would give the same output as the examples above. Whether you should use either method to achieve the desired goal is entirely up to you.

If you want to pass an indeterminate number of parameters to be dealt with in sub MAIN, you can use slurpy parameters:

# inside file 'hello-all.raku'
sub MAIN(*@all) { @all.map: -> $name { say "Hello, " ~ $name } }
$ raku hello-all.raku peter paul mary
Hello, peter
Hello, paul
Hello, mary

A more complicated example using a single positional and multiple named parameters, and also showing that where clauses can also be applied to MAIN arguments:

# inside "frobnicate.raku"
sub MAIN(
  Str   $file where *.IO.f = 'file.dat',
  Int  :$length = 24,
  Bool :$verbose
) {
    say $length if $length.defined;
    say $file   if $file.defined;
    say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}

With file.dat present, this will work this way:

$ raku frobnicate.raku
24
file.dat
Verbosity off

Or this way with --verbose:

$ raku frobnicate.raku --verbose
24
file.dat
Verbosity on

If the file file.dat is not present, or you've specified another filename that doesn't exist, you would get the standard usage message created from introspection of the MAIN sub:

$ raku frobnicate.raku doesnotexist.dat
Usage:
  frobnicate.raku [--length=<Int>] [--verbose] [<file>]

Although you don't have to do anything in your code to do this, it may still be regarded as a bit terse. But there's an easy way to make that usage message better by providing hints using pod features:

# inside "frobnicate.raku"
sub MAIN(
  Str   $file where *.IO.f = 'file.dat',  #= an existing file to frobnicate
  Int  :$length = 24,                     #= length needed for frobnication
  Bool :$verbose,                         #= required verbosity
) {
    say $length if $length.defined;
    say $file   if $file.defined;
    say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}

Which would improve the usage message like this:

$ raku frobnicate.raku doesnotexist.dat
Usage:
  frobnicate.raku [--length=<Int>] [--verbose] [<file>]

    [<file>]          an existing file to frobnicate
    --length=<Int>    length needed for frobnication
    --verbose         required verbosity

From release 2021.03, values to single named arguments can be separated by spaces too. Consider a demo program with the following source:

subset name of Any where Str|True;
subset port of Str;
multi MAIN(
    $file,
    name :$profile,    #= Write profile information to a file
    port :$debug-port, #= Listen for debugger connections on the specified port
    Bool :v($verbose), #= Display verbose output

) {}
multi MAIN("--process-files", *@images) {}

This program generates the following usage message:

Usage:
  demo [--profile[=name]] [--debug-port=<port>] [-v] <file>
  demo --process-files [<images> ...]

    --profile[=name]       Write profile information to a file
    --debug-port=<port>    Listen for debugger connections on the specified port
    -v                     Display verbose output

The following are valid ways to call demo:

demo --profile ~/foo
demo --profile=/tmp/bar ~/foo
demo --debug-port 4242 ~/foo
demo --debug-port=4242 ~/foo
demo -v ~/foo
demo --process-files *.jpg

These, however, are not valid

demo --profile /tmp/bar ~/foo
demo --debug-port ~/foo

The first is invalid because /tmp/bar and ~/foo are both parsed as positional arguments, which means demo was called with too many positional arguments. The second is invalid because ~/foo is parsed as an argument to --debug-port, and thus demo lacks the required positional argument.

Here's how it works; with Raku distinguishing between three types of options:

  • Boolean options (like -v), which never take an argument; they are ether present or absent.

  • Options with a mandatory argument (like --debug-port), which always take an argument. If you give them an argument with =, they will use that; if not, they'll take the following argument.

  • Options with an optional argument (like --profile), which are valid both with and without an argument. You can only give these arguments an option with the = syntax; if there is a space after the option, that means it was called without an argument.

And here's the signature that produces each type of argument:

  • Boolean options: A Bool type constraint.

  • Options with a mandatory argument: A type that does not .ACCEPT a Bool.

  • Options with an optional argument: A type that .ACCEPTS a True (because passing an option without an argument is equivalent to passing True)

As any other subroutine, MAIN can define aliases for its named parameters.

sub MAIN(
  Str   $file where *.IO.f = 'file.dat',  #= an existing file to frobnicate
  Int  :size(:$length) = 24,              #= length/size needed for frobnication
  Bool :$verbose,                         #= required verbosity
) {
    say $length if $length.defined;
    say $file   if $file.defined;
    say 'Verbosity ', ($verbose ?? 'on' !! 'off');
}

In which case, these aliases will also be listed as alternatives with --help:

Usage:
  frobnicate.raku [--size|--length=<Int>] [--verbose] [<file>]

    [<file>]                 an existing file to frobnicate
    --size|--length=<Int>    length needed for frobnication
    --verbose                required verbosity

Enumerations can be used in signatures with arguments converted automatically to its corresponding enum symbol:

enum Flag  (
    FLAG_FOO => 0b001,
    FLAG_BAR => 0b010,
    FLAG_BAZ => 0b100,
);

sub MAIN(Flag $flag = FLAG_FOO) {
    say "Flagging $flag";
}

This will work correctly with

raku MAIN-enum.raku FLAG_BAR

but will die if called with something that is not a Flag.

In Functions§

See primary documentation in context for sub MAIN

Declaring a sub MAIN is not compulsory in Raku scripts, but you can provide one to create a command line interface for your script.