Command Line Arguments in Raku

Raku 中的命令行参数

  • MAIN 子例程
  • 命名参数和位置参数
    • 命名参数
    • 位置参数
    • Multi dispatch
    • 组合命名参数和位置参数
  • 可选参数和必选参数
  • 别名或备用命名参数
  • USAGE 子例程

Sub MAIN

在 Raku 中,命令行参数的解析是使用 MAIN 子例程完成的,MAIN 子例程是一个特殊的子例程,它根据签名解析命令行参数。与其他子程序一样,您可以使用命名参数和位置参数,可选(和必需)参数,multiple dispatch 等。 通过定义 MAIN 子例程,编译器会自动生成 USAGE 子例程。可以修改此子例程以返回自定义的 usage 信息。所有的命令行参数也可以在特殊变量 @*ARGS 中使用,它可以在 MAIN 处理之前进行更改。

命名参数和位置参数

命名参数

我们从一个简单的程序开始(保存为 prog.p6):

use v6;

sub MAIN(
    Str :$name = 'John', 
    Str :$last-name = 'Doe',
) {

    my $formatted-name = "$name.tc() $last-name.tc()";
    say $formatted-name;
}

在这个 MAIN 子程序中,我们创建了两个命名参数,$name$last-name,带有类型约束(Str),方法是在子例程的签名给每个变量添加 :。这些参数还具有默认值,这是通过为参数赋值来实现的。在这种情况下,我们将 $name 设置为默认值 ‘John’,将 $last-name 设置为 ‘Doe’。如果执行 prog.p6 时命令行参数与 MAIN 签名匹配,则会打印出格式化的全名:

$ raku prog.p6
John Doe
$ raku prog.p6 --name='carl' --last-name='sagan'
Carl Sagan
$ raku prog.p6 --last-name='sagan' --name='carl'
Carl Sagan

如您所见,命名参数可以按您想要的任意顺序传递。 如果没有匹配 MAIN 签名,那么我们会收到一条 usage 信息:

$ p6 prog.p6 --name='Carl' --last-name='Sagan' --career='astronomer'
prog.p6 [--name=<Str>] [--last-name=<Str>]

位置参数

如果我们想要使用位置参数,我们可以重新定义子例程的签名以仅解析位置参数。与之前的版本一样,我们将拥有参数的默认值,但这些参数现在是位置的,必须按签名定义的顺序提供:

use v6;

sub MAIN(
    Str $name = 'John',       # No colon(:) in the variable 
    Str $last-name = 'Doe',   # No colon(:) in the variable 
) {

    my $formatted-name = "$name.tc() $last-name.tc()";
    say $formatted-name;
}

使用匹配的签名执行 prog.p6 将打印以下输出:

$ raku prog.p6
John Doe

$ raku prog.p6 carl sagan
Carl Sagan

没有一个,它给我们以下 usage 信息:

$ raku prog.p6 carl sagan astronomer
prog.p6 [<name>] [<last-name>]

multiple dispatch

我们可能更喜欢在我们的小程序中使用命名参数和位置参数。正如我们之前提到的,我们可以使用 multiple dispatch(具有相同名称但具有不同签名的多个子例程)来声明具有其自己的签名的多个 MAIN 子例程。为此,每个候选者都使用 multi 关键字而不是 sub 来声明:

use v6;

multi MAIN(
    Str :$name = 'John', 
    Str :$last-name = 'Doe',
) {

    my $formatted-name = "$name.tc() $last-name.tc()";
    say $formatted-name;
}

multi MAIN(
    Str $name = 'John', 
    Str $last-name = 'Doe',
) {

    my $formatted-name = "$name.tc() $last-name.tc()";
    say $formatted-name;
}

两个 MAIN 子例程看起来非常相似,但它们具有不同的签名,这些签名描述了预期的命令行参数。 如果我们使用与任何 MAIN 签名匹配的命令行参数执行 prog.p6,我们将获得格式化的全名:

$ p6 prog.p6 --name='ada' --last-name='lovelace'
Ada Lovelace

$ p6 prog.p6 marcus aurelius
Marcus Aurelius

如果没有匹配的签名,我们将获得一个有用的 usage 信息,详细说明 MAIN 子例程的可能签名:

$ p6 prog.p6 --name='Ada' --last-name='Lovelace' --title='Ms'
Usage:
  prog.p6 [--name=<Str>] [--last-name=<Str>] 
  prog.p6 [<name>] [<last-name>] 

组合命名参数和位置参数

在我们的例子中定义不同的签名来处理不同的命令行参数,命名参数和位置参数在我们的例子中是很好的。但是,如果您想在单个 MAIN 签名中混合命名参数和位置参数,该怎么办?尽管必须在命名参数之前定义位置参数,但这可以非常容易地完成。 让我们通过向第一个 multi 子例程添加位置参数来更新我们的简单程序 prog.p6 的最新迭代:

use v6;

multi MAIN(
    Str $title = 'Mr',      # Our positional parameter defined before named ones
    Str :$name = 'John', 
    Str :$last-name = 'Doe',
) {

    my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";
    say $formatted-name;
}

multi MAIN(
    Str $title,
    Str $name = 'John', 
    Str $last-name = 'Doe',
) {

    my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";
    say $formatted-name;
}

可选参数和必选参数

默认情况下,命名参数是可选的。尽管如此,可以通过在相应的词法变量后面附加一个 ! 来标记它们。例如,MAIN( :$first, :$second, :$operator ){ ... } 如果没有一些命令行参数但是 MAIN( :$first!, :$second!, :$operator! ){ ... } 假设现在需要参数并且调用者必须传递必要的参数,$ operator!){…}会这样做。

另一方面,默认情况下需要位置参数,但可以通过将相应的词汇变量附加 ? 来将其标记为可选。例如,如果在没有命令行参数但是 MAIN( $first, $second, $operator ){ ... } 的情况下调用MAIN($ first,$ second,$ operator){…}将打印一条用法消息不会因为参数现在是可选的。 通过设置默认值,也可以将位置参数定义为可选,例如我们如何使用$MAIN和$ MAIN中的$ last-name($ title,$ name =‘John’,$ last-name =‘Doe’){.. 。}。

别名或备用命名参数

命名参数及其别名通过使用冒号对语法(:)提供。冒号的存在:将决定我们是否正在创建一个新的命名参数。 让我们修改prog.p6中的第一个多数来包含一些别名:

use v6;

multi MAIN(
    Str $title = 'Mr',
    Str :$name = 'John', 
    Str :last-name($surname) = 'Doe',
    Bool :p(:$print),
) {

    my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
    
    if $print {
        say $formatted-name;
    }
}

...

这个MAIN定义了两种别名: :last-name($ surname)只将传递给命令行参数–last-name的内容别名为变量$ surname(注意缺少:)。这意味着$ surname将只是别名变量的名称,它不会创建新的命名参数:

$ p6 prog.p6 --name='alan' --last-name='turing' -p
Alan Turing

$ p6 prog.p6 --name='alan' --surname='turing'
Usage:
  pos-named.p6 [--name=<Str>] [--last-name=<Str>] [-p|--print] [<title>]
  • :$ print不仅是别名变量的名称,还会是一个新的命名参数,旁边:p:
$ p6 prog.p6 --name='alan' --last-name='turing'

$ p6 prog.p6 --name='alan' --last-name='turing' -p
Alan Turing

$ p6 prog.p6 --name='alan' --last-name='turing' -print
Alan Turing

您可能已经注意到,如果我们要打印人员的格式化全名,则必须现在指定标志-p(或-print)。这是因为Bool类型使$ print成为二进制标志,如果不存在则为False。如果被调用,则标志为True,这使得我们可以执行简单的if * print {…}语句。 使用别名是为参数创建长格式和短格式选项名称的简便方法。我们可以进一步修改prog.p6中的第一个multi,以便为–name和–last-name提供一个简短的表单选项名称:

use v6;

multi MAIN(
    Str $title = 'Mr',
    Str :n(:$name) = 'John', 
    Str :l(:last-name($surname)) = 'Doe',
    Bool :p(:$print),
) {

    my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
    
    if $print {
        say $formatted-name;
    }
}
...

通过使用不同的表单选项执行prog.p6,我们得到:

p6 prog.p6 --name='alan' --last-name='turing' -print
Mr. Alan Turing

p6 prog.p6 -n='grace' -l='hopper' -p 'Ms'
Ms. Grace Hopper

如果没有匹配的签名,我们会收到以下用法消息:

p6 prog.p6 -n='alan' -l='turing' -p --career='mathematician'
Usage:
  prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>] 
  prog.p6 [<title>] [<name>] [<last-name>]

Sub USAGE

如果没有匹配的签名,我们的小程序prog.p6的最新版本将打印以下用法消息:

Usage:
  prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>] 
  prog.p6 <title> [<name>] [<last-name>] 

这是由于在MAIN子程序没有提供匹配的签名时自动调用USAGE子程序。如果没有找到这样的子例程,编译器将输出默认生成的使用消息,这意味着我们可以定义它以提供更详细的(如果我们想要的话!)用法消息。 这是带有修改的USAGE子的prog.p6:

use v6;

multi MAIN(
    Str $title = 'Mr',
    Str :n(:$name) = 'John', 
    Str :l(:last-name($surname)) = 'Doe',
    Bool :p(:$print),
) {

    my $formatted-name = "$title.tc() $name.tc() $surname.tc()";
    
    if $print {
        say $formatted-name;
    }
}

multi MAIN(
    Str $title = 'Mr',
    Str $name = 'John', 
    Str $last-name = 'Doe',
) {

    my $formatted-name = "$title.tc() $name.tc() $last-name.tc()";

    say $formatted-name;
}

sub USAGE() {
print Q:c:to/END/;
Usage:
  {$*PROGRAM-NAME} [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>] 
  {$*PROGRAM-NAME} [<title>] [<name>] [<last-name>] 

optional arguments:
  -h, --help                     show this help message and exit
  -n=PERSON_NAME, --name=PERSON_NAME
                                 specify person's name
  -l=PERSON_LAST_NAME, --last-name=PERSON_LAST_NAME
                                 specify person's last name
  -p , --print                   print person's full name
  <title>                        specify person's title ('Mr' by default)

  Examples:
    {$*PROGRAM-NAME} --name='richard' --last-name='feynman' -p
    {$*PROGRAM-NAME} --name='sophie' --last-name='germain' -p 'Ms'
    {$*PROGRAM-NAME} 'leonhard' 'euler'
END
}

注意在使用消息中提到-h(和–help)标志,我们不需要显式定义它们,因为它们是自动生成的。如果我们现在使用–help(或-h)标志执行prog.p6或不提供匹配的签名,我们将获得新的用法消息:

Usage:
  prog.p6 [-n|--name=<Str>] [-l|--last-name=<Str>] [-p|--print] [<title>] 
  prog.p6 [<title>] [<name>] [<last-name>] 

optional arguments:
  -h, --help                     show this help message and exit
  -n=PERSON_NAME, --name=PERSON_NAME
                                 specify person's name
  -l=PERSON_LAST_NAME, --last-name=PERSON_LAST_NAME
                                 specify person's last name
  -p , --print                   print person's full name
  <title>                        specify person's title ('Mr' by default)

  Examples:
   prog.p6 --name='richard' --last-name='feynman' -p
   prog.p6 --name='sophie' --last-name='germain' -p 'Ms'
   prog.p6 'leonhard' 'euler'

结论

这当然只是对MAIN和USAGE子程序的肤浅看法。就像在Raku中一样,总有比眼睛更多的东西。例如,如果您希望将命名参数放在命令行中的任何位置(即使在位置参数之后),您可以修改散列%* SUB-MAIN-OPTS以允许此行为。如果您想了解更多细节,我在下面提供了一些有用的链接。

也可以看看:

Raku 

comments powered by Disqus