我假设以下块代表一个函数,但它可能不是:
mounted()true
if
...
fi ... && mounted
我想了解那个构造是什么。我在zsh的函数标题下没有找到类似的语法
更完整的实际代码片段是:
#! /bin/zsh -p
# leaving out a section...
tmpdir=$(mktemp -d) || exit
mounted()true
if
mount "$type[@]" -o "${(j[,])opts}" -- "$dev" "$tmpdir"
then
mount --bind -- "$tmpdir/$subdir" "$dest" || mounted()false
umount -- "$tmpdir"
fi && rmdir -- "$tmpdir" && mounted
一旦我理解了它,我将把整个 zsh 脚本转换成我更熟悉的语言。我可能会将其转换为 bash 作为中间步骤。
那是定义函数的 Bourne shell 语法(从 80 年代开始),它并不特定于 zsh。
在 Bourne shell 中,函数是通过粘贴
functionName()
在命令前面来定义的¹。所以
name() true
定义了一个name
用true
简单命令调用的函数作为它的主体。几乎所有的 Bourne-like shell(ksh、ash、dash、bosh、pdksh、mksh、zsh ......)都是这种情况。一个值得注意的例外是
bash
²(GNU shell),它最初只允许命令组({ ...; }
) 作为函数的主体,后来将其更改为POSIX语言要求的任何复合命令。sh
因此,在 中
bash
,您需要name()((1))
orname()[[ . ]]
((( ... ))
和[[ ... ]]
— 从 ksh 借来的构造 — 被视为那里的复合命令),或者name() { true; }
函数的主体是一个命令组,里面只有一个简单的命令。请注意,这
ksh
是首先引入函数的 shell,尽管语法不同:function name { body; }
.zsh
支持 Korn 和 Bourne 语法,并有自己的扩展。在
zsh
seeinfo zsh function
中,您应该会看到关于函数定义语法的 Kornfunction
关键字的部分。一个扩展zsh
是您可以使用相同的主体一次定义多个函数,并使用任何字符串作为函数名称³:定义几个不同的调用
true
或false
不带参数的函数。如果你省略函数名,它会变成一个匿名函数,它可以接受参数并被当场调用:
(尽管出于显而易见的原因,对于能够接受参数的匿名函数,它的主体不能是一个简单的命令)。
在
zsh
中,函数也可以通过$functions
特殊的关联数组获得,其中键是函数名,值是主体中的代码。functions[name]=true
定义name
函数的另一种方法也是如此。现在,使用函数来存储布尔值并不是你经常看到的,但如果你停下来想一想,它确实很有意义。
例如,在 C 语言中,
if
/while
构造或&&
/||
逻辑运算符作用于数字。ifif (condition) something
是一个非零数。或者哪些是类 C 语言将其扩展到被定义或成为非空字符串。something
condition
awk
perl
condition
但是 shell 在所有命令行解释器之前。在 shell 中,一切都是命令。
if
/while
和&&
/||
在大多数 shell 中作用于命令。if condition; then something; fi
如果命令成功something
则执行。condition
false
并且true
是总是分别失败/成功的布尔常量命令(内置在大多数 shell 中),因此它们是表示布尔值的明显命令。函数是存储命令(或一般的 shell 代码)的最合适的数据结构。alias
es(许多人认为它是 csh 的破坏遗产,一个 shell(就像最初的 Bourne shell)没有功能)在这里不起作用,因为别名在读取包含它们的代码时扩展,而不是在运行时扩展。例如在:函数体实际上将包含
if false; then...
,因为name
别名是在读取定义函数的代码时扩展的,而不是在函数运行时。可以将代码存储在变量而不是函数中:
我们在哪里测试命令是否成功,该
eval
命令被告知解释$name
4中的代码。请注意,在这种情况下,空/未定义(nounset
选项除外)会$name
产生true。或者我们可以这样做(这就是我通常在
sh
/bash
脚本中所做的):我们在其中运行名称存储在
$name
没有参数的命令的位置。或者:
我们将简单命令的参数存储在
$name
数组变量中。习惯于使用类 C 语言的人可能希望将布尔值存储为整数变量并运行诸如
[
/之类的命令,test
或者expr
在从文本表示转换时测试整数的值:在类似 Korn 的 shell(包括
bash
和zsh
)中,您可以使用((...))
计算类似 C 的算术表达式并在产生非零的数字(甚至是 NaN)时返回成功的构造。您还可以运行一个比较字符串的命令(例如
[
/test
/expr
再次),或者像其他类似 Korn 的[[ string = pattern ]]
构造一样进行模式匹配:(这听起来对我来说就像
if (strcmp(name, "true") == 0)...
在 C 中做 a 一样陌生)。或者即使您喜欢,也可以对已定义的变量进行类似
awk
/perl
的测试。¹ Bourne shell 中有一个错误(不是在它的克隆/衍生物5中),但是如果您使用带有重定向的简单命令作为函数的主体,则该错误无法正常工作,这可能是 POSIX 只需要复合命令的原因被支持为函数体。
²
yash
(根据 POSIX 规范编写),posh
(基于pdksh
但编写以帮助验证是否符合标准,因此删除了对标准的大多数扩展,包括该标准)是另外两个例外³ 这与外部命令和命令参数可以是任何字符串的事实是一致的,因为文件可以包含任何字符串(尽管文件名不能为空并且文件名/参数不能包含 NUL 字节)。在 Bourne shell 中,函数和变量名称共享相同的命名空间(您不能定义具有相同名称的函数和变量),并且函数名称与变量名称具有相同的限制。
4前面有一个空格以确保正确性,因为某些
eval
实现不支持--
标记选项的结尾5 zsh,在函数体中使用重定向时,本身曾经有自己的问题,其中大部分已修复。但即使是现在,正如文档中明确指出的那样,in
f() { cmd; } < $1
,(在主体是具有重定向的命令组的特殊情况下)$1
不是指$1
函数范围内的,而是$1
指调用者的该实例使其不符合 POSIX 标准。这不适用于简单命令或其他类型的复合命令(zsh
内部包含在 中{...}
)。