自定义命令
Nu 具备组合长管道的能力使你对数据和系统有很强的控制力,但它的代价是需要大量的键盘输入。不过理想情况下,你可以保存精心设计的管道以便反复使用。
这就是自定义命令(Custom Commands)的作用。
下面看一个自定义命令的例子:
1. def greet [name] {
2. echo "hello" $name
3. }
在这个定义中,我们定义了greet命令,它需要一个参数name。在这个参数后面是自定义命令运行时将执行的代码块。当被调用时,自定义命令将把传递给name的值设置为$name变量,该变量在块内是可用的。
要运行上述命令,我们可以像调用内置命令一样调用它:
1. > greet "world"
当我们这样做的时候,就会得到输出,如同我们使用内置命令一样:
1. ───┬───────
2. 0 │ hello
3. 1 │ world
4. ───┴───────
TIP
echo将其参数分别返回给管道。如果你想用它来生成一个单一的字符串,请在管道中添加| str collect:
1. def greet [name] {
2. echo "hello " $name | str collect
3. }
5. greet nushell
返回 hello nushell
命令名称
在 Nushell 中,命令名是一串字符或一个带引号的字符串。下面是一些有效命令名的例子:greet, get-size, mycommand123, "mycommand", 😊, 和123。
注意:在 Nushell 中,通常的做法是用-来分隔命令的多个单词,以提高可读性。 例如,使用 get-size 而不是 getsize 或者 get_size。
子命令
你也可以使用空格来定义命令的子命令(Subcommand)。例如,如果我们想给str添加一个新的子命令,可以通过命名我们的子命令以 “str” 开头来做到。比如:
1. def "str mycommand" [] {
2. echo hello
3. }
现在我们可以像调用str的内置子命令一样调用我们的自定义命令:
1. > str mycommand
参数类型
在定义自定义命令时,你可以为每个参数命名并选择性地设置其类型。例如,你可以把上面的内容写成:
1. def greet [name: string] {
2. echo "hello " $name | str collect
3. }
参数的类型是可选的。Nushell 支持不添加类型,此时会把参数类型当作any。如果你在参数上标注了一个类型,Nushell 将在你调用函数的时候检查该类型。
例如,假设你需要输入一个int类型:
1. def greet [name: int] {
2. echo "hello " $name | str collect
3. }
5. greet world
如果我们尝试运行上述内容,Nushell 会告诉我们,类型不匹配:
1. error: Type Error
2. ┌─ shell:6:7
3. │
4. 5 │ greet world
5. │ ^^^^^ Expected int, found world
这可以帮助指导你的用户只使用支持的类型来调用你所定义的命令。
目前可以支持的类型是(从 0.59.0 版本开始):
anyblockcell-pathdurationpathexprfilesizeglobintmathnumberoperatorrangecondboolsignaturestringvariable
具有默认值的参数
若要使一个参数成为可选的,并具有默认值,你可以在命令定义中指定该默认值:
1. def greet [name = "nushell"] {
2. echo "hello " $name | str collect
3. }
你可以在没有参数的情况下调用这个命令,也可以指定一个值来覆盖默认值:
1. > greet
2. hello nushell
3. > greet world
4. hello world
你也可以将默认值与类型要求相结合:
1. def congratulate [age: int = 18] {
2. echo "Happy birthday! Wow you are " $age " years old now!" | str collect
3. }
如果你想检查一个可选参数是否存在,而不是仅仅依赖一个默认值,请使用可选位置参数代替。
可选位置参数
默认情况下,位置参数(Positional Parameters)是必须的。如果没有传递位置参数,我们将遇到一个报错:
1. × Missing required positional argument.
2. ╭─[entry #23:1:1]
3. 1 │ greet
4. · ▲
5. · ╰── missing name
6. ╰────
7. help: Usage: greet <name>
我们可以在一个位置参数的名字后面加上一个问号(?),将其标记为可选参数。比如:
1. def greet [name?: string] {
2. echo "hello" $name | str collect
3. }
5. greet
使一个位置参数成为可选参数并不改变它在命令体中被访问的名称。如上例所示,尽管参数列表中有?的后缀,但它仍然以$name的形式被访问。
当一个可选参数没有被传递,它在命令体中的值等于null和$nothing。我们可以利用这一点来对没有传递参数的情况进行处理:
1. def greet [name?: string] {
2. if ($name == null) {
3. echo "hello, I don't know your name!"
4. } else {
5. echo "hello " $name | str collect
6. }
7. }
9. greet
如果你只是想在参数缺失时设置一个默认值,那么使用默认值来代替就更简单了。
如果必需的和可选的位置参数一起使用,那么必需的参数必须先出现在定义中。
标志
除了传递位置参数之外, 你还可以通过为自定义命令定义标志(Flags)来传递命名参数。
比如:
1. def greet [
2. name: string
3. --age: int
4. ] {
5. echo $name $age
6. }
在上面的greet定义中,我们定义了name位置参数以及age标志。这允许greet的调用者也可以选择传递age参数。
你可以用以下方法调用上述内容:
1. > greet world --age 10
或者:
1. > greet --age 10 world
或者甚至完全不使用标志:
1. > greet world
标志也可以指定一个缩写版本,这允许你传递一个更简单的标志,如同传递一个更容易阅读的全写标志那样。
让我们扩展前面的例子,为age添加一个缩写标志:
1. def greet [
2. name: string
3. --age (-a): int
4. ] {
5. echo $name $age
6. }
注意: 标志是以其全称命名的,所以上面的例子的命令体内需要使用$age而不是$a。
现在,我们可以使用缩写标志来调用这个新的定义:
1. > greet -a 10 hello
剩余参数
在某些情况下, 你可能想定义一个需要任意数量的位置参数的命令。我们可以用一个剩余参数(Rest Parameter)来实现这一点,通过下面的...语法:
1. def greet [...name: string] {
2. echo "hello all:"
3. for $n in $name {
4. echo $n
5. }
6. }
8. greet earth mars jupiter venus
我们可以使用任意数量的参数来调用上述greet命令的定义,包括完全没有参数,所有的参数都将被收集到$name列表中。
剩余参数可以和位置参数一起使用:
1. def greet [vip: string, ...name: string] {
2. echo "hello to our VIP " $vip | str collect
3. echo "and hello to everybody else:"
4. for $n in $name {
5. echo $n
6. }
7. }
9. # $vip $name
10. # ---- ------------------------
11. greet moon earth mars jupiter venus
为命令添加文档
为了更好地帮助用户使用你的自定义命令,也可以为其添加额外的命令和参数描述。
以我们之前的例子为例:
1. def greet [
2. name: string
3. --age (-a): int
4. ] {
5. echo $name $age
6. }
一旦定义完毕,我们可以运行help greet来获得该命令的帮助信息:
1. Usage:
2. > greet <name> {flags}
4. Parameters:
5. <name>
7. Flags:
8. -h, --help: Display this help message
9. -a, --age <integer>
你可以看到我们定义的参数和标志,以及所有命令都会得到的-h帮助标志。
为了改进这个帮助,我们可以在定义中加入描述,这些描述将在帮助中显示出来:
1. # A greeting command that can greet the caller
2. def greet [
3. name: string # The name of the person to greet
4. --age (-a): int # The age of the person
5. ] {
6. echo $name $age
7. }
我们给定义和参数添加的注释会作为描述出现在命令的help中。
现在,如果我们运行help greet,就会得到一些更友好的帮助文本:
1. A greeting command that can greet the caller
3. Usage:
4. > greet <name> {flags}
6. Parameters:
7. <name> The name of the person to greet
9. Flags:
10. -h, --help: Display this help message
11. -a, --age <integer>: The age of the person
管道输出
自定义命令会像内置命令一样流式输出。例如,假设我们想重构这个管道:
1. > ls | get name
让我们把ls移到我们编写的命令中:
1. def my-ls [] { ls }
我们就可以像使用ls一样使用这个命令的输出:
1. > my-ls | get name
2. ───┬───────────────────────
3. 0 │ myscript.nu
4. 1 │ myscript2.nu
5. 2 │ welcome_to_nushell.md
6. ───┴───────────────────────
这让我们可以很容易地创建自定义命令并处理它们的输出。注意,我们不像其他语言那样使用返回语句,取而代之的是我们创建管道,而其输出数据流可以连接到其他管道。
管道输入
如同其他命令一样,自定义命令也可以从管道中获取输入,这个输入会自动传递给自定义命令所使用的代码块。
让我们创建一个把所有接收值都加倍的命令:
1. def double [] {
2. each { |it| 2 * $it }
3. }
现在,如果我们在一个管道中调用上述命令,就可以看到它对输入的处理结果:
1. > [1 2 3] | double
2. ───┬─────
3. 0 │ 2
4. 1 │ 4
5. 2 │ 6
6. ───┴─────
我们还可以使用$in变量来存储输入,以便在后面使用:
1. def nullify [...cols] {
2. let start = $in
3. $cols | reduce --fold $start { |col, df|
4. $df | upsert $col null
5. }
6. }
持久化
关于如何持久化自定义命令,以便在你启动 Nushell 时它们是可用的,请参阅 配置 部分并添加你的启动脚本。
