自定义命令
与任何编程语言一样,你很快就会想要保存较长的管道和表达式,以便在需要时可以轻松地再次调用它们。
这就是自定义命令的作用。
注意
自定义命令与许多语言中的函数类似,但在 Nushell 中,自定义命令本身就是一等公民。正如你将在下面看到的,它们与内置命令一起包含在帮助系统中,可以成为管道的一部分,会实时进行类型错误解析等等。
创建和运行自定义命令
让我们从一个简单的 greet 自定义命令开始:
def greet [name] {
$"Hello, ($name)!"
}在这里,我们定义了 greet 命令,它接受一个参数 name。此参数后面是表示自定义命令运行时将发生什么的块。调用时,自定义命令会将为 name 传递的值设置为 $name 变量,该变量将在块内可用。
要运行此命令,我们可以像调用内置命令一样调用它:
greet "World"
# => Hello, World!从命令返回值
你可能会注意到上面的示例中没有 return 或 echo 语句。
像 PowerShell 和 JavaScript(使用箭头函数)等其他一些语言一样,Nushell 具有隐式返回的特性,即命令中最后一个表达式的值成为其返回值。
在上面的示例中,只有一个表达式——字符串。此字符串成为命令的返回值。
greet "World" | describe
# => string当然,一个典型的命令将由多个表达式组成。为了演示,这里有一个包含 3 个表达式的无意义命令:
def eight [] {
1 + 1
2 + 2
4 + 4
}
eight
# => 8返回值同样只是命令中最后一个表达式的结果,即 4 + 4 (8)。
其他示例:
提前返回
由于某种情况需要提前退出的命令仍然可以使用 return 语句返回值。
def process-list [] {
let input_length = length
if $input_length > 10_000 {
print "Input list is too long"
return null
}
$in | each {|i|
# Process the list
$i * 4.25
}
}抑制返回值
你通常会希望创建一个作为语句而不是表达式的自定义命令,并且不返回值。
在这种情况下,你可以使用 ignore 关键字:
def create-three-files [] {
[ file1 file2 file3 ] | each {|filename|
touch $filename
} | ignore
}如果没有管道末尾的 ignore,该命令将从 each 语句返回一个空列表。
你也可以返回一个 null 作为最后一个表达式。或者,在这个人为的例子中,使用一个不返回值的 for 语句(见下一个例子)。
不返回值的语句
Nushell 中的一些关键字是语句,不返回值。如果你使用这些语句之一作为自定义命令的最后一个表达式,则返回值将为 null。在某些情况下,这可能是意料之外的。例如:
def exponents-of-three [] {
for x in [ 0 1 2 3 4 5 ] {
3 ** $x
}
}
exponents-of-three上面的命令将不显示任何内容,返回值为 null,因为 for 是一个不返回值的语句。
要从输入列表中返回值,请使用诸如 each 命令之类的过滤器:
def exponents-of-three [] {
[ 0 1 2 3 4 5 ] | each {|x|
3 ** $x
}
}
exponents-of-three
# => ╭───┬─────╮
# => │ 0 │ 1 │
# => │ 1 │ 3 │
# => │ 2 │ 9 │
# => │ 3 │ 27 │
# => │ 4 │ 81 │
# => │ 5 │ 243 │
# => ╰───┴─────╯Match 表达式
# 返回当前目录中的一个随机文件
def "random file" [] {
let files = (ls)
let num_files = ($files | length)
match $num_files {
0 => null # 对于空目录返回 null
_ => {
let random_file = (random int 0..($num_files - 1))
($files | get $random_file)
}
}
}在这种情况下,最后一个表达式是 match 语句,它可以返回:
- 如果目录为空,则返回
null - 否则,返回一个表示随机选择的文件的
record:::
自定义命令和管道
与内置命令一样,自定义命令的返回值可以传递到管道中的下一个命令。自定义命令也可以接受管道输入。此外,只要有可能,管道输入和输出都会在可用时进行流式处理。
重要!
另请参阅:管道
管道输出
ls | get name让我们将 ls 移动到我们编写的命令中:
def my-ls [] { ls }我们可以像使用 ls 一样使用此命令的输出。
my-ls | get name
# => ╭───┬───────────────────────╮
# => │ 0 │ commands │
# => │ 1 │ myscript.nu │
# => │ 2 │ myscript2.nu │
# => │ 3 │ welcome_to_nushell.md │
# => ╰───┴───────────────────────╯这使我们可以轻松地构建自定义命令并处理其输出。请记住,我们不像其他语言那样使用 return 语句。相反,隐式返回允许我们构建输出数据流的管道,这些数据流可以连接到其他管道。
注意
在这种情况下,ls 内容仍然是流式的,即使它在一个单独的命令中。在慢速(例如,网络)文件系统上对长目录运行此命令将会在行可用时返回它们。
管道输入
自定义命令也可以从管道中获取输入,就像其他命令一样。此输入会自动传递给自定义命令的块。
让我们创建自己的命令,将接收到的每个值都加倍:
def double [] {
each { |num| 2 * $num }
}现在,如果我们在管道中稍后调用上述命令,我们可以看到它对输入的作用:
[1 2 3] | double
# => ╭───┬───╮
# => │ 0 │ 2 │
# => │ 1 │ 4 │
# => │ 2 │ 6 │
# => ╰───┴───╯酷!
此命令演示了输入和输出流式处理。尝试使用无限输入运行它:
1.. | each {||} | double | first 3
# => ╭───┬───╮
# => │ 0 │ 2 │
# => │ 1 │ 4 │
# => │ 2 │ 6 │
# => ╰───┴───╯即使输入命令永远不会结束,double 命令仍然可以在值可用时接收和输出它们,因此 first 取得它的三个值后管道就会停止。
如果没有 first 3,管道将永远运行下去。按 Ctrl+C 停止它。
我们还可以使用 $in 变量存储输入以供以后使用:
def nullify [...cols] {
let start = $in
$cols | reduce --fold $start { |col, table|
$table | upsert $col null
}
}
ls | nullify name size
# => ╭───┬──────┬──────┬──────┬───────────────╮
# => │ # │ name │ type │ size │ modified │
# => ├───┼──────┼──────┼──────┼───────────────┤
# => │ 0 │ │ dir │ │ 8 minutes ago │
# => │ 1 │ │ file │ │ 8 minutes ago │
# => │ 2 │ │ file │ │ 8 minutes ago │
# => │ 3 │ │ file │ │ 8 minutes ago │
# => ╰───┴──────┴──────┴──────┴───────────────╯命名命令
在 Nushell 中,命令名可以是一串字符。以下是一些有效命令名的示例:greet、get-size、mycommand123、my command、命令,甚至 😊。
应避免可能与其他解析器模式混淆的字符串。例如,以下命令名可能无法调用:
1、"1"或"1.5":Nushell 不允许使用数字作为命令名4MiB或"4MiB":Nushell 不允许使用文件大小作为命令名"number#four"、"number^four"或"number%four":命令名中不允许使用插入符号、哈希符号和百分号"foo bar"、" foo"或"foo ":命令名中的单词必须用单个空格分隔,不能有前导或尾随空白。这样的名称永远无法被调用,因此def、extern和alias会拒绝它。-a、"{foo}"、"(bar)":将无法调用,因为 Nushell 会将它们解释为标志、闭包或表达式。
解析器关键字,例如 if、for、let、def、type、use 或 where,根本不能用作命令名,因为遮蔽它们会破坏其所实现的语言结构:
def if [] { "my if" }
# => Error: nu::parser::name_is_keyword
# =>
# => × Can't use parser keyword `if` as command name.
# => ╭─[repl_entry #1:1:5]
# => 1 │ def if [] { "my if" }
# => · ─┬
# => · ╰── 'if' is a parser keyword
# => ╰────
# => help: Parser keywords cannot be shadowed (including via module exports and `use *`). Choose a different command name so language constructs keep working.虽然像 "+foo" 这样的名称可能有效,但最好避免使用,因为解析器规则可能会随着时间的推移而改变。如有疑问,请使命令名尽可能简单。
提示
在 Nushell 中,通常的做法是用 - 分隔命令的多个单词以提高可读性。例如,使用 get-size 而不是 getsize 或 get_size。
提示
因为 def 是一个解析器关键字,所以命令名必须在解析时已知。这意味着命令名不能是变量或常量。例如,以下是不允许的:
let name = "foo"
def $name [] { foo }
# => Error: nu::parser::unknown_state
# =>
# => × Unknown state.
# => ╭─[repl_entry #1:2:5]
# => 1 │ let name = "foo"
# => 2 │ def $name [] { foo }
# => · ──┬──
# => · ╰── Could not get string from string expression
# => ╰────子命令
你还可以使用空格定义命令的子命令。例如,如果我们想向 str 添加一个新子命令,我们可以通过以“str ”开头命名我们的子命令来创建它。例如:
def "str mycommand" [] {
"hello"
}现在我们可以像调用 str 的内置子命令一样调用我们的自定义命令:
str mycommand
# => hello当然,名称中带空格的命令也以同样的方式定义:
def "custom command" [] {
"This is a custom command with a space in the name!"
}遮蔽内置命令
自定义命令可以与内置命令同名。此时该自定义命令会遮蔽内置命令:调用这个名称会运行你的版本。要仍然调用内置命令,可以在其名称前加上 % 符号,这与 ^ 强制调用外部命令很像:
def echo [...args] { "echo is turned off" }
echo hello
# => echo is turned off
%echo hello
# => hello当内置命令的名称存储在变量中(%$cmd)或在子表达式中计算(%($cmd))时,% 符号同样有效:
let cmd = "echo"
%$cmd hello
# => hello只有内置命令可以这样调用。对自定义命令、别名或未知名称使用 % 是一个解析错误:
def greet [] { "hi" }
%greet
# => Error: nu::parser::error
# =>
# => × percent sigil requires a built-in command
# => ╭─[repl_entry #1:2:2]
# => 1 │ def greet [] { "hi" }
# => 2 │ %greet
# => · ──┬──
# => · ╰── unknown built-in command
# => ╰────
# => help: remove `%` to use normal resolution, or use `^` to run an external command explicitly提示
在遮蔽了内置命令的命令内部,% 非常方便,因为命令体可以调用原始命令而不会递归调用自身。替换 ls 的完整示例参见别名。
参数
多个参数
在 def 命令中,参数在 list 中定义。这意味着多个参数可以用空格、逗号或换行符分隔。
例如,这里是一个接受两个名称的 greet 版本。这三个定义中的任何一个都可以工作:
# 空格
def greet [name1 name2] {
$"Hello, ($name1) and ($name2)!"
}# 逗号
def greet [name1, name2] {
$"Hello, ($name1) and ($name2)!"
}# 换行符
def greet [
name1
name2
] {
$"Hello, ($name1) and ($name2)!"
}必需的位置参数
上面使用的基本参数定义是位置的。传递给上面 greet 命令的第一个参数被分配给 name1 参数(并且,如上所述,是 $name1 变量)。第二个参数成为 name2 参数和 $name2 变量。
默认情况下,位置参数是必需的。使用我们之前定义的带有两个必需位置参数的 greet:
def greet [name1, name2] {
$"Hello, ($name1) and ($name2)!"
}
greet Wei Mei
# => Hello, Wei and Mei!
greet Wei
# => Error: nu::parser::missing_positional
# =>
# => × Missing required positional argument.
# => ╭─[repl_entry #3:1:10]
# => 1 │ greet Wei
# => ╰────
# => help: Usage: greet <name1> <name2> . Use `--help` for more information.提示
尝试在此版本的 greet 之后键入第三个名称。请注意,解析器会自动检测到错误,并在执行前将第三个参数突出显示为错误。
可选的位置参数
我们可以通过在位置参数的名称后加上问号 (?) 来将其定义为可选。例如:
def greet [name?: string] {
$"Hello, ($name | default 'You')"
}
greet
# => Hello, You提示
请注意,用于访问变量的名称不包括 ?;只有其在命令签名中的定义才包括。
当未传递可选参数时,其在命令主体中的值为 null。上面的示例使用 default 命令在 name 为 null 时提供默认值“You”。
你也可以直接比较该值:
def greet [name?: string] {
match $name {
null => "Hello! I don't know your name!"
_ => $"Hello, ($name)!"
}
}
greet
# => Hello! I don't know your name!如果必需和可选的位置参数一起使用,则必需的参数必须首先出现在定义中。
带默认值的参数
你还可以为缺少的参数设置默认值。带默认值的参数在调用命令时也是可选的。
def greet [name = "Nushell"] {
$"Hello, ($name)!"
}你可以不带参数调用此命令,也可以带一个值来覆盖默认值:
greet
# => Hello, Nushell!
greet world
# => Hello, world!你还可以将默认值与类型注解结合起来:
def congratulate [age: int = 18] {
$"Happy birthday! You are ($age) years old now!"
}参数类型
对于每个参数,你可以选择性地定义其类型。例如,你可以将基本的 greet 命令写成:
def greet [name: string] {
$"Hello, ($name)"
}如果参数没有类型注解,Nushell 会将其视为 any 类型。如果你在参数上注解了类型,Nushell 会在你调用函数时检查其类型。
例如,假设你只想接受一个 int 而不是 string:
def greet [name: int] {
$"hello ($name)"
}如果我们用字符串调用它,Nushell 会告诉我们类型不匹配:
greet World
# => Error: nu::parser::parse_mismatch
# =>
# => × Parse mismatch: expected int.
# => ╭─[repl_entry #2:1:7]
# => 1 │ greet World
# => · ──┬──
# => · ╰── expected int
# => ╰────
# => help: Check the syntax around this position — a typo, missing delimiter, or wrong separator is common.酷!
类型检查是解析器的一项功能。在命令行中输入自定义命令时,Nushell 解析器甚至可以实时检测到无效的参数类型,并在执行命令前将其突出显示。
突出显示样式可以使用主题或手动使用 $env.config.color_config.shape_garbage 进行更改。
类型注解列表
大多数类型都可以用作类型注解。此外,还有一些“形状”可以使用。例如:
number: 接受int或floatpath:被视为文件系统路径的字符串。前导~会展开为主目录,而诸如...这样的“n 点”简写会展开为../..。相对路径保持相对:.仍然是.,而foo/../bar变成bar。有关示例用法,请参阅语言参考指南中的路径。directory:path(上文)的子集。使用制表符补全参数时,只会提供目录。扩展方式与path相同。external_arg:以外部命令解析参数的方式解析该参数。裸词会保持输入时的原样,因此0001或true不会被转换为int或bool,而带引号的字符串、变量和子表达式则保留各自的值。这主要用于从另一个 shell 接收参数的脚本的main命令。oneof<...>:接受所列类型中的任意一个,例如oneof<int, string>。字面量参数会取它匹配的第一个所列类型,而文本类类型(string、path、directory、glob、cell-path、external_arg和any)无论其位置如何都会最后尝试。因此对于oneof<string, list<string>>,[foo bar]是一个列表;而对于oneof<string, int>,5(以及007)是一个int。如果你需要文本,请给参数加引号。error:可用,但目前没有已知的有效用法。有关更多信息,请参阅语言参考指南中的错误。
例如,比较一个无类型参数和一个 external_arg 参数:
def as-any [value] { $value }
def as-external [value: external_arg] { $value }
as-any 0001
# => 1
as-external 0001
# => 0001以下类型可用于参数注解:
anybinaryboolcell-pathclosuredatetimedurationfilesizefloatglobintlistnothingrangerecordstringtable
类型别名
当多个命令接收或返回相同的结构化数据时,每次都写出 record<x: float, y: float> 这样的类型会很长,而且容易写错。type 关键字可以为类型命名:
type Point = record<x: float, y: float>
type Polygon = list<Point>
def first-x [poly: Polygon]: nothing -> float { $poly.0.x }
first-x [{x: 1.0, y: 2.0} {x: 3.0, y: 4.0}]
# => 1.0类型别名只是其类型的简短写法。解析器会在使用名称的地方把名称替换为完整类型,因此 help、describe 和错误消息显示的都是完整类型:
type Point = record<x: float, y: float>
def length-of [p: Point]: nothing -> float { ($p.x ** 2 + $p.y ** 2) | math sqrt }
length-of {x: 1, y: 2}
# => Error: nu::parser::type_mismatch
# =>
# => × Type mismatch.
# => ╭─[repl_entry #3:1:11]
# => 1 │ length-of {x: 1, y: 2}
# => · ──────┬─────
# => · ╰── expected record<x: float, y: float>, found record<x: int, y: int>
# => ╰────类型别名可以用在任何需要类型注解的地方:位置参数、标志、剩余参数、闭包参数、输入/输出类型、let、mut 和 const 的注解、其它类型内部(例如 list<Point>),以及另一个 type 的右侧。
以下几点规则适用:
- 与
alias和const一样,类型别名从定义处到所在块结束处可见。它必须定义在首次使用之前,且不能引用自身。 - 自定义补全器属于参数,而不属于类型。应写在参数上,例如
def paint [color: Color@[red green blue]] { ... }。type Color = string@[red green blue]是错误的。 - 在模块中,类型别名默认是私有的,除非用
export type定义。如何导出参见模块,如何导入参见使用模块。
注意
type 是一个解析器关键字,因此自定义命令、别名或 extern 不能命名为 type。要运行名为 type 的外部命令,请以 ^type 调用,或使用 Nushell 的 which 查看某个命令名指向什么。
标志
除了位置参数,你还可以定义命名标志。
例如:
def greet [
name: string
--age: int
] {
{
name: $name
age: $age
}
}在此版本的 greet 中,我们定义了 name 位置参数以及一个 age 标志。位置参数(因为它没有 ?)是必需的。命名标志是可选的。不带 --age 标志调用命令会将 $age 设置为 null。
--age 标志可以放在位置 name 之前或之后。示例:
greet Lucia --age 23
# => ╭──────┬───────╮
# => │ name │ Lucia │
# => │ age │ 23 │
# => ╰──────┴───────╯
greet --age 39 Ali
# => ╭──────┬─────╮
# => │ name │ Ali │
# => │ age │ 39 │
# => ╰──────┴─────╯
greet World
# => ╭──────┬───────╮
# => │ name │ World │
# => │ age │ │
# => ╰──────┴───────╯标志也可以用简写版本定义。这允许你传递一个更简单的标志以及一个更易于阅读的长标志。
让我们扩展前面的示例,为 age 值使用一个简写标志:
def greet [
name: string
--age (-a): int
] {
{
name: $name
age: $age
}
}提示
结果变量始终基于长标志名称。在上面的示例中,变量仍然是 $age。$a 将无效。
现在,我们可以使用简写标志调用此更新后的定义:
greet Akosua -a 35
# => ╭──────┬────────╮
# => │ name │ Akosua │
# => │ age │ 35 │
# => ╰──────┴────────╯标志也可以用作基本开关。存在时,基于开关的变量为 true。不存在时,为 false。
def greet [
name: string
--caps
] {
let greeting = $"Hello, ($name)!"
if $caps {
$greeting | str uppercase
} else {
$greeting
}
}
greet Miguel --caps
# => HELLO, MIGUEL!
greet Chukwuemeka
# => Hello, Chukwuemeka!你还可以将其分配给 true/false 以启用/禁用该标志:
greet Giulia --caps=false
# => Hello, Giulia!
greet Hiroshi --caps=true
# => HELLO, HIROSHI!提示
请注意以下错误:
greet Gabriel --caps true
# => Error: nu::parser::extra_positional
# =>
# => × Extra positional argument.
# => ╭─[repl_entry #1:1:22]
# => 1 │ greet Gabriel --caps true
# => · ──┬─
# => · ╰── extra positional argument
# => ╰────
# => help: Usage: greet {flags} <name>键入空格而不是等号会将 true 作为位置参数传递,这可能不是期望的结果!这里 greet 只接受一个位置参数,因此 Nushell 会报错。
为避免混淆,不允许在标志上注解布尔类型:
def greet [
--caps: bool # Not allowed
] { $caps }
# => Error: nu::parser::error
# =>
# => × Type annotations are not allowed for boolean switches.
# => ╭─[repl_entry #1:2:13]
# => 1 │ def greet [
# => 2 │ --caps: bool # Not allowed
# => · ──┬─
# => · ╰── Remove the `: bool` type annotation.
# => 3 │ ] { $caps }
# => ╰────标志可以包含破折号。可以通过在结果变量名中用下划线替换破折号来访问它们:
def greet [
name: string
--all-caps
] {
let greeting = $"Hello, ($name)!"
if $all_caps {
$greeting | str uppercase
} else {
$greeting
}
}值为 null 的标志
当标志被赋予 null 作为其值时,结果取决于该标志的类型:
- 如果类型不接受
nothing(例如--age: int),Nushell 会将其视为未传入该标志,因此使用其默认值(或null)。 - 如果类型接受
nothing(例如--age: any或--age: oneof<int, nothing>),null会被原样传递给命令。
这让编写一个把可选标志转发给另一个命令的命令变得容易。在下面的示例中,我们将使用这个版本的 greet:
def greet [
name: string
--age: int = 18
--caps
] {
let greeting = $"Hello, ($name)! You are ($age)."
if $caps { $greeting | str uppercase } else { $greeting }
}party 转发它自己的 --age 标志。当调用者省略它时,$age 为 null,因此 greet 回退到其默认值:
def party [name: string, --age: int] {
greet $name --age=$age
}
party Kai
# => Hello, Kai! You are 18.
party Kai --age 30
# => Hello, Kai! You are 30.从记录传递标志
你还可以使用展开运算符(...)把记录作为命名标志传递。每个字段名就是一个标志名(不带 --),每个值成为该标志的值。对于开关,true 会设置该标志,而 false 或 null 则不设置:
let options = { age: 30, caps: true }
greet Kai ...$options
# => HELLO, KAI! YOU ARE 30.
greet Kai ...{ age: null, caps: false }
# => Hello, Kai! You are 18.字段值会按标志定义进行类型检查,不匹配任何标志的字段是一个错误:
greet Kai ...{ colour: red }
# => Error: nu::shell::error
# =>
# => × Unknown flag `colour` in spread record
# => ╭─[repl_entry #1:1:14]
# => 1 │ greet Kai ...{ colour: red }
# => · ───────┬───────
# => · ╰── `colour` is not a named argument of this command
# => ╰────剩余参数
在某些情况下,你可能想定义一个需要任意数量的位置参数的命令。我们可以用一个“剩余”参数来实现这一点,使用下面的 ... 语法:
def multi-greet [...names: string] {
for $name in $names {
print $"Hello, ($name)!"
}
}
multi-greet Elin Lars Erik
# => Hello, Elin!
# => Hello, Lars!
# => Hello, Erik!我们可以用任意数量的参数调用上面 multi-greet 命令的定义,包括完全没有参数。所有参数都将收集到 $names 列表中。
剩余参数可以和位置参数一起使用:
def vip-greet [vip: string, ...names: string] {
for $name in $names {
print $"Hello, ($name)!"
}
print $"And a special welcome to our VIP today, ($vip)!"
}
# $vip $name
# ----- -------------------------
vip-greet Rahul Priya Arjun Anjali Vikram
# => Hello, Priya!
# => Hello, Arjun!
# => Hello, Anjali!
# => Hello, Vikram!
# => And a special welcome to our VIP today, Rahul!要将列表传递给剩余参数,你可以使用展开运算符 (...)。使用上面的 vip-greet 命令定义:
let vip = "Tanisha"
let guests = [ Dwayne, Shanice, Jerome ]
vip-greet $vip ...$guests
# => Hello, Dwayne!
# => Hello, Shanice!
# => Hello, Jerome!
# => And a special welcome to our VIP today, Tanisha!用 -- 结束标志解析
以 - 开头的参数通常会被解析为标志,因此把 -x 这样的值作为位置参数传递是一个错误。与 POSIX shell 一样,单独的 -- 会结束标志解析。它之后的每个参数都被视为位置参数,即使以 - 开头,而 -- 本身不会传递给命令:
def show-args [...args] { $args }
show-args -- --verbose -x foo
# => ╭───┬───────────╮
# => │ 0 │ --verbose │
# => │ 1 │ -x │
# => │ 2 │ foo │
# => ╰───┴───────────╯这对内置命令同样有效,例如 [a b] | str join -- -x 返回 a-xb。用 def --wrapped 定义的命令(见下文)是个例外:它们会在剩余参数中收到 --,因此可以原样传递给它们所包装的外部命令。
带包装的外部命令的剩余参数
使用 def --wrapped 定义的自定义命令会将任何未知的标志和参数收集到一个 剩余参数中,然后可以通过列表展开将其传递给外部命令。这允许 自定义命令“包装”和扩展外部命令,同时仍然接受其所有原始参数。 例如,外部 eza 命令显示目录列表。 默认情况下,它显示网格排列:
eza commands
# => categories docs README.md我们可以定义一个新命令 ezal,它将始终显示长列表,并添加图标:
def --wrapped ezal [...rest] {
eza -l ...$rest
}注
你也可以添加 --icons。我们在此示例中省略它, 只是因为这些图标在本指南中显示效果不佳。
请注意,--wrapped 会将任何其他参数强制放入 rest 参数中,因此 可以使用 eza 支持的任何参数调用该命令。这些附加参数将通过列表展开 操作 ...$rest 进行扩展。
ezal commands
# => drwxr-xr-x - ntd 7 Feb 11:41 categories
# => drwxr-xr-x - ntd 7 Feb 11:41 docs
# => .rw-r--r-- 936 ntd 14 Jun 2024 README.md
ezal -d commands
# => drwxr-xr-x - ntd 14 Jun 2024 commands自定义命令可以检查某些参数并相应地更改其行为。例如,当使用 -G 选项 强制使用网格时,我们可以省略向 eza 传递 -l:
def --wrapped ezal [...rest] {
if '-G' in $rest {
eza ...$rest
} else {
eza -l --icons ...$rest
}
}
ezal -G commands
# => categories docs README.md管道输入输出签名
默认情况下,自定义命令接受 <any> 类型作为管道输入,同样可以输出 <any> 类型。但自定义命令也可以被赋予明确的签名以缩小允许的类型范围。
例如,str stats 的签名如下所示:
def "str stats" []: string -> record { {} }在这里,string -> record 定义了命令的管道输入和输出的允许类型:
- 它接受一个
string作为管道输入 - 它输出一个
record
注意
带有输入输出签名的命令体必须产生声明的输出类型。空命令体({ })会输出它的输入,在这里是一个 string,因此 Nushell 会报错。这就是上面的占位命令体返回一个空记录({})的原因。
如果有多个输入/输出类型,它们可以放在括号内并用逗号或换行符分隔,如 str join 中所示:
def "str join" [separator?: string]: [
list -> string
string -> string
] { "" }这表示 str join 可以接受 list<any> 或 string 作为管道输入。在任何一种情况下,它都将输出一个 string。
有些命令不接受或不需要数据作为管道输入。在这种情况下,输入类型将是 <nothing>。如果命令返回 null(例如,rm 或 hide),输出类型也是如此:
def xhide [module: string, members?]: nothing -> nothing { }注意
上面的示例被重命名为 xhide,以便将其复制到 REPL 不会遮蔽内置的 hide 命令。
输入输出签名显示在命令的 help 中(包括内置和自定义命令),也可以通过以下方式进行内省:
help commands | where name == <command_name>
scope commands | where name == <command_name>酷!
输入输出签名允许 Nushell 在解析时捕获另外两类错误:
尝试从命令返回错误的类型。例如:
def inc []: int -> int { $in + 1 print "Did it!" } # => Error: nu::parser::output_type_mismatch # => # => × Command output doesn't match int. # => ╭─[repl_entry #1:1:24] # => 1 │ ╭─▶ def inc []: int -> int { # => 2 │ │ $in + 1 # => 3 │ │ print "Did it!" # => 4 │ ├─▶ } # => · ╰──── expected int, but command outputs nothing # => ╰────以及尝试将无效类型传递给命令:
def inc []: int -> int { $in + 1 } "Hi" | inc # => Error: nu::parser::input_type_mismatch # => # => × Command does not support string input. # => ╭─[repl_entry #1:2:8] # => 1 │ def inc []: int -> int { $in + 1 } # => 2 │ "Hi" | inc # => · ─┬─ # => · ╰── command doesn't support string input # => ╰────
为你的命令添加文档
为了更好地帮助用户理解如何使用你的自定义命令,你还可以为它们添加额外的命令和参数描述。
运行 help vip-greet 来检查我们上面定义的最新命令:
Usage:
> vip-greet <vip> ...(names)
Flags:
-h, --help: Display the help message for this command
Command Type:
> custom
Parameters:
vip <string>
...names <string>
Input/output types:
╭───┬───────┬────────╮
│ # │ input │ output │
├───┼───────┼────────┤
│ 0 │ any │ any │
╰───┴───────┴────────╯酷!
你可以看到 Nushell 仅根据我们到目前为止的定义就自动为该命令创建了一些基本帮助。Nushell 还自动向该命令添加了一个 --help/-h 标志,因此用户也可以使用 vip-greet --help 访问帮助。
我们可以通过一些简单的注释来进一步扩展帮助,描述命令及其参数:
# 问候客人和一位贵宾
#
# 用于生日、毕业派对、
# 退休,以及任何其他
# 庆祝特定人物事件的
# 活动。
def vip-greet [
vip: string # 特别嘉宾
...names: string # 其他客人
] {
for $name in $names {
print $"Hello, ($name)!"
}
print $"And a special welcome to our VIP today, ($vip)!"
}现在再次运行 help vip-greet 来看看有什么不同:
Greet guests along with a VIP
Use for birthdays, graduation parties,
retirements, and any other event which
celebrates an event # for a particular
person.
Usage:
> vip-greet <vip> ...(names)
Flags:
-h, --help: Display the help message for this command
Command Type:
> custom
Parameters:
vip <string>: The special guest
...names <string>: The other guests
Input/output types:
╭───┬───────┬────────╮
│ # │ input │ output │
├───┼───────┼────────┤
│ 0 │ any │ any │
╰───┴───────┴────────╯请注意,def 语句前紧接的注释行成为帮助系统中命令的描述。可以使用多行注释。第一行(在空注释行之前)成为帮助的 description。此信息在制表符补全命令时也会显示。
其余的注释行成为其在帮助数据中的 extra_description。
提示
运行:
scope commands
| where name == 'vip-greet'
| wrap help这将显示 Nushell 创建的帮助记录。
参数后面的注释成为它们的描述。对于参数,只允许单行注释。
注意
用于参数文档目的的同一行上的 Nushell 注释需要在 # 磅号前有一个空格。
通过 attr 为自定义命令添加属性
从 0.103.0 版本开始,Nushell 允许自定义命令的作者在命令定义之前用 attr 内置命令的子命令来丰富自己的命令,这些子命令以 @ 符号为前缀。 对于来自 Python、Java 或 JavaScript 的人来说, 这看起来类似于装饰器或注解,但用途略有不同。
让我们改进 vip-greet 自定义命令的文档。example 属性会添加到 help {command} 或 {command} -h 的输出中:
# 问候客人和一位贵宾
#
# 用于生日、毕业派对、
# 退休,以及任何其他
# 庆祝特定人物事件的
# 活动。
@example "Greet a VIP" {vip-greet "Bob"} --result "And a special welcome to our VIP today, Bob!"
@example "Greet multiple people" {vip-greet "Bob" "Alice" "Charlie"} --result "Hello, Alice!
Hello, Charlie!
And a special welcome to our VIP today, Bob!"
def vip-greet [
vip: string # 特别嘉宾
...names: string # 其他客人
] {
for $name in $names {
print $"Hello, ($name)!"
}
print $"And a special welcome to our VIP today, ($vip)!"
}现在,运行 help vip-greet 就能看到新增的示例。
Greet guests along with a VIP
Use for birthdays, graduation parties,
retirements, and any other event which
celebrates an event # for a particular
person.
Usage:
> vip-greet <vip> ...(names)
Flags:
-h, --help: Display the help message for this command
Command Type:
> custom
Parameters:
vip <string>: The special guest
...names <string>: The other guests
Input/output types:
╭───┬───────┬────────╮
│ # │ input │ output │
├───┼───────┼────────┤
│ 0 │ any │ any │
╰───┴───────┴────────╯
Examples:
Greet a VIP
> vip-greet "Bob"
And a special welcome to our VIP today, Bob!
Greet multiple people
> vip-greet "Bob" "Alice" "Charlie"
Hello, Alice!
Hello, Charlie!
And a special welcome to our VIP today, Bob!在维护用 nu 编写的脚本、模块、插件或库时,某些自定义命令 可能最终会被更新的版本取代或被完全移除, 但仍会临时保留一段时间, 以便用户有时间从旧功能过渡过来。
deprecated 属性会在运行该自定义命令时 向用户发出警告。
@deprecated
def greet [
name: string
--all-caps
] {
let greeting = $"Hello, ($name)!"
if $all_caps {
$greeting | str uppercase
} else {
$greeting
}
}运行 greet {name} 就能看到该警告。
greet "bob"
# => Warning: nu::parser::deprecated
# =>
# => ⚠ Command deprecated.
# => ╭─[repl_entry #2:1:1]
# => 1 │ greet "bob"
# => · ─────┬─────
# => · ╰── greet is deprecated and will be removed in a future release.
# => ╰────
# =>
# => Hello, bob!默认情况下,警告只在该命令于会话中首次使用时显示。 添加 --report every 可在每次调用时都发出警告。
如果有替代命令或额外的上下文可以帮助用户更新其工作流, 可以在 @deprecated 之后添加文本。
category 属性会在使用 scope commands 或 help 命令时赋予指定的标签。
@deprecated "Use vip-greet as a replacement." --report every
@category "deprecated"
def greet [
name: string
--all-caps
] {
let greeting = $"Hello, ($name)!"
if $all_caps {
$greeting | str uppercase
} else {
$greeting
}
}greet bob
# => Warning: nu::parser::deprecated
# =>
# => ⚠ Command deprecated.
# => ╭─[repl_entry #2:1:1]
# => 1 │ greet bob
# => · ────┬────
# => · ╰── greet is deprecated and will be removed in a future release.
# => ╰────
# => help: Use vip-greet as a replacement.
# =>
# => Hello, bob!
help commands | where category == deprecated | select name category command_type
# => ╭───┬───────┬────────────┬──────────────╮
# => │ # │ name │ category │ command_type │
# => ├───┼───────┼────────────┼──────────────┤
# => │ 0 │ greet │ deprecated │ custom │
# => ╰───┴───────┴────────────┴──────────────╯属性也适用于 extern 声明。其中 @structured 仅适用于 extern。它让外部命令返回数据而不是文本。 示例参见结构化输出。
要查看 Nushell 中可用的其它属性,请查阅命令参考, 或在 Nushell REPL 中运行 help attr。
在自定义命令中更改环境
通常,环境变量定义和更改在块内是作用域的(作用域)。这意味着当它们在块结束时超出作用域时,对这些变量的更改将丢失,包括自定义命令的块。
def foo [] {
$env.FOO = 'After'
}
$env.FOO = "Before"
foo
$env.FOO
# => Before但是,使用 def --env 或 export def --env(对于模块)定义的命令将在调用方保留环境:
def --env foo [] {
$env.FOO = 'After'
}
$env.FOO = "Before"
foo
$env.FOO
# => After在自定义命令中更改目录 (cd)
同样,使用 cd 命令更改目录会导致 $env.PWD 环境变量的更改。这意味着当自定义命令结束时,目录更改($env.PWD 变量)也将被重置。如上所述,解决方案是使用 def --env 或 export def --env。
def --env go-root [] {
cd /
}
cd ~
go-root
pwd
# => /持久化
要使自定义命令在将来的 Nushell 会话中可用,你需要将它们添加到你的启动配置中。你可以将命令定义添加到:
- 直接在你的
config.nu中 - 由你的
config.nu加载的文件中 - 由你的
config.nu导入的模块中
有关更多详细信息,请参阅配置章节。