iBu*_*Bug 5 shell bash ksh zsh mksh
在 Android(使用mkshMirBSD Korn Shell)上,有一种特殊的字符串替换语法(称为“值替换”):
${|commands}
Run Code Online (Sandbox Code Playgroud)
替换结果不是收集命令的输出(如``和),而是从分隔符内分配的变量$()中获取。$REPLY它的特殊之处在于命令不在子 shell 中运行 - 它们在同一个 shell 中运行并且可以访问当前 shell 会话拥有的所有内容。
Debian 有一个mksh适用于 MirBSD Korn Shell 的软件包,其行为与 Android 完全相同。
哪些 shell 支持类似的语法,其中:
该功能被维护者或 MirBSD 及其 shell (源自 pdksh) Thorsten Glaser(又名@mirabilos )称为值替换或valsub,是特定于.mkshmksh
它于 2013 年 5 月 2 日提交给 mksh 代码库,并于第二天在 mksh 邮件列表上发布 R46 版本。
${ body; }它写在命令替换形式(在 中称为函数替换或funsub)的背面,该形式mksh是同年 2 月从 ksh93 复制的,也在 R46 中发布。
在 ksh93 中,内置的 I/O 被虚拟化。既不$(builin-cmd)涉及${ builtin-cmd; }任何分叉或 I/O。所以$(print foo)还是${ print foo; }扩展到foo并满足你对不涉及任何fd的算子的要求。
在这两种形式中,print内置函数不会向任何 fd 写入任何内容,但其可能的输出(删除尾随换行符)构成了扩展。两者的区别在于$(...)引入了子shell环境(与其他shell相反,它不是通过fork子进程来实现的),而${ ...; }没有引入。
现在,为了能够做到这一点,ksh93(几乎从头开始重写 ksh(本身来自 1983 年)),shell 中的所有 I/O 都必须专门重写。当 mksh${ ...; }在 2013 年添加该功能时,它采用了一种更简单的方法,只需将输出记录在已删除的临时文件中,并在其中的代码返回后读取该文件的内容以弥补扩展。
然而,这意味着输出最终会存储在磁盘上,即使是暂时的,而且 I/O 意味着比结果数据像 ksh93 那样在内存中传递的性能更差。所以我想这就是为什么 Thorsten 添加了${| ...; }单独的形式,它可以使用专用变量 ( ) 传递值$REPLY,并且不需要对 shell 内部进行重大修改。
然而,这意味着以这种方式使用的函数必须专门编写以返回它们的值$REPLY(除了通过 split+glob 之外,只能是标量而不是列表),并且只是变成了一些语法糖。例子:
sanitize() {
REPLY=${1//[!0123456789-]}
local sign=
case $REPLY in
(-*) REPLY=${REPLY#-}; sign=-
esac
REPLY=$sign${REPLY//-}
}
print "$(( ${|sanitize "$1"} + ${|sanitize "$2"} ))"
Run Code Online (Sandbox Code Playgroud)
没有它,你必须写:
sanitize "$1"; a=$REPLY
sanitize "$2"; b=$REPLY
print "$(( a + b ))"
Run Code Online (Sandbox Code Playgroud)
$(...)与和相比的一个优点${ ...; }是它不会删除尾随的换行符。例如,是错误的,因为如果以换行符结尾则$(basename -- "$file")不起作用,而 while (假设被重写为返回 中的基本名称的函数)不会有问题。$file${|basename -- "$file"}basename$REPLY
其他具有可以在不涉及 I/O 的情况下返回值的构造的 shell:
确实有人在 2019 年提出实现 mksh 的 valsub 的简化版本,最终演变成这个提案,但据我所知,它还没有实现zsh。
2024 编辑 mksh${|...}和 ksh93${...;}现已在 zsh 中实现,预计将在 6.0 或 5.9 之后的下一个版本中提供。还有一个${|var|...}变体允许返回除 之外的变量中的值$REPLY。
然而,zsh 有几种替代方法可以使扩展成为任意代码的结果,而不涉及子 shell 或 I/O。
对于算术,有数学函数zsh的概念:
square() (( $1 * $1))
functions -M square 1
echo $(( square(5) + square(12) ))
Run Code Online (Sandbox Code Playgroud)
但这仅限于数字(整数或浮点数),并且只能在算术表达式中使用。数学函数本身可以使用 ) 将非数字作为参数functions -sM,因此虽然非常复杂,但您可以这样做:
func() REPLY=foo$1; functions -sM func
echo ${$((func(bar)))+$REPLY} ${$((func(baz)))+$REPLY}
Run Code Online (Sandbox Code Playgroud)
相当于 的mksh:
func() REPLY=foo$1
echo "${|func bar}" "${|func baz}"
Run Code Online (Sandbox Code Playgroud)
zsh有另一种形式的扩展,可以用没有 I/O 的 shell 代码来计算。这是使用称为动态命名目录的波浪号扩展自定义框架(请参阅 参考资料info zsh dynamic)。
如果您定义:
autoload -Uz add-zsh-hook
valsub() {
[[ $1 = n && $2 = '!'* ]] && eval "${2#?}" && reply=("$REPLY")
}
add-zsh-hook -Uz zsh_directory_name valsub
Run Code Online (Sandbox Code Playgroud)
然后,该形式的波浪号扩展~[!'REPLY=something']将扩展为something。
波浪号扩展并非在每个上下文中都完成,但您也可以使用动态命名目录功能作为参数扩展的一部分,使用上面提到的有关 valsub 支持的讨论中描述的那种技巧。
eGlob 也可以使用(用于求值) 或glob 限定符扩展到任意代码的结果+。
这些通常用于根据某些代码的结果过滤文件。
喜欢:
ls -ld -- *.txt(e['(( $#REPLY > 20 ))'])
Run Code Online (Sandbox Code Playgroud)
要选择长度大于 20 个字符的 txt 文件名。但也可以用来改变扩展的结果:
ls -ld -- *.txt(e['REPLY=$REPLY:r.html'])
Run Code Online (Sandbox Code Playgroud)
(展开到txt扩展名替换为 的文件html)。甚至:
ls -ld -- *.txt(e['reply+=($REPLY:r.html)'])
Run Code Online (Sandbox Code Playgroud)
返回txt和html翻译。
所以你实际上可以这样做:
echo /(e['REPLY=foobar'])
Run Code Online (Sandbox Code Playgroud)
为了将其扩展到任意代码的结果,这里应用/我们知道始终存在的限定符。甚至是一个列表:
printf '<%s>\n' /(e['reply=(foo bar)'])
Run Code Online (Sandbox Code Playgroud)
限定符+是一个仅采用函数名称的变体,因此您可以执行生成扩展的函数在echo /(+func)where的操作。func
同样,与~扩展一样,通配符并不是在所有情况下都完成的。
es是 Byron Rakitzis 的 Research Unix V10/Plan9 rcshell 的公共域克隆的衍生品。
rc的函数可以返回退出状态列表(可以是信号名称或正整数),并在列表变量中可供调用者使用$status。
es将其扩展为能够返回任何内容的任何列表,并且不是使其在 中可用$status,而是使用语法获取退出状态(或函数返回值)<={...}。
所以你可以这样做:
fn foo { return foo$1 }
echo <={foo bar}
Run Code Online (Sandbox Code Playgroud)
例如。
但请注意,只有由空列表或元素全为空或 0 的列表组成的返回值才会被解释为成功。因此,例如,这里foo anything && echo bar永远不会输出bar,因为foo总是返回一个永远不会被解释为success 的值。
除此之外$(...),${ ...; }已经讨论过,还有一个功能允许扩展具有动态内容而不涉及 I/O:
您可以定义一个在每次设置或扩展变量时调用的函数。对于关联数组变量,这些函数将有权访问下标,因此您可以使用它将一个任意参数传递给函数:
typeset -A valsub
function valsub.get {
.sh.value=foo${.sh.subscript}
}
echo "${valsub[bar]}"
Run Code Online (Sandbox Code Playgroud)
会输出foobar.
ksh93 也有数学函数,但语法与以下函数不同zsh:
function .sh.math.square x {((.sh.value = x*x))}
echo "$(( square(5) + square(12) ))"
Run Code Online (Sandbox Code Playgroud)
小智 1
${ cmds;}ksh93 的命令替换形式在同一 shell 中运行,cmds但以其他方式捕获标准输出,就像常规命令替换一样。例子:
a=1; echo ${ a=2; echo wtf;}; echo $a
wtf
2
Run Code Online (Sandbox Code Playgroud)
事实上,它捕获命令的标准输出正是它的有用之处,因为您不必将输出保存到某个临时文件中,然后将其读回,或设置命名管道,或重写一些毛茸茸的函数使其将输出附加到某个变量,而不是仅仅写出来。
这与“价值替代” mksh 功能非常不同,我无法找到任何理由。为什么不能REPLY先分配变量,然后将其用作$REPLY?