桀骜

桀骜

在 Android(使用mkshMirBSD Korn Shell)上,有一种特殊的字符串替换语法(称为“值替换”):

${|commands}

替换结果不是收集命令的输出(如``和),而是从分隔符内分配的变量$()中获取。$REPLY它的特殊之处在于命令不要在子 shell 中运行 - 它们在同一个 shell 中运行,并且可以访问当前 shell 会话拥有的所有内容。

Debian 有一个mksh适用于 MirBSD Korn Shell 的软件包,其行为与 Android 完全相同。

哪些 shell 支持类似的语法,其中:

  • 命令在与父级相同的 shell 中运行
  • 替换结果是从文件描述符以外的其他方式获取的
    • 应该很容易注意到读取输出或管道算作 FD

答案1

该功能称为价值替代或者瓦尔苏布作者:托尔斯滕·格拉泽(又名@米拉比洛斯),维护者或 MirBSD 及其外壳mksh(源自 pdksh),特定于mksh.

它是于 2013 年 5 月 2 日承诺加入 mksh 代码库,并于第二天在 mksh 邮件列表上发布 R46 版本。

${ body; }它写在命令替换形式的背面(称为功能替代或者有趣的潜艇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"} ))"

没有它,你必须写:

sanitize "$1"; a=$REPLY
sanitize "$2"; b=$REPLY
print "$(( a + b ))"

$(...)与和相比的一个优点${ ...; }是它不会删除尾随的换行符。例如,是错误的,因为如果以换行符结尾则$(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) ))

但这仅限于数字(整数或浮点数),并且只能在算术表达式中使用。数学函数本身可以使用functions -sM) 将非数字作为参数,因此虽然非常复杂,但您可以这样做:

func() REPLY=foo$1; functions -sM func

echo ${$((func(bar)))+$REPLY} ${$((func(baz)))+$REPLY}

相当于mksh的:

func() REPLY=foo$1

echo "${|func bar}" "${|func baz}"

动态命名目录

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

然后,该形式的波浪号扩展~[!'REPLY=something']将扩展为something

波形符扩展并非在所有情况下都完成,但您也可以使用它动态命名目录使用该功能作为参数扩展的一部分上面提到的关于 valsub 支持的讨论中描述了一种技巧

e 和 + 全局限定符

Glob 也可以使用以下命令扩展为任意代码的结果e(for评估) 或+全局限定符。

这些通常用于根据某些代码的结果过滤文件。

喜欢:

ls -ld -- *.txt(e['(( $#REPLY > 20 ))'])

要选择长度大于 20 个字符的 txt 文件名。但也可以用来改变扩展的结果:

ls -ld -- *.txt(e['REPLY=$REPLY:r.html'])

(展开到txt扩展名替换为 的文件html)。甚至:

ls -ld -- *.txt(e['reply+=($REPLY:r.html)'])

返回txthtml翻译。

所以你实际上可以这样做:

echo /(e['REPLY=foobar'])

为了将其扩展到任意代码的结果,这里应用我们知道始终存在的限定符/。甚至是一个列表:

printf '<%s>\n' /(e['reply=(foo bar)'])

限定符+是一个仅接受函数名称的变体,因此您可以执行生成扩展的函数在echo /(+func)哪里。func

同样,与~扩展一样,通配符并不是在所有情况下都完成的。

英语

es是 Byron Rakitzis 的 Research Unix V10/Plan9 rcshell 的公共域克隆的衍生品。

rc的函数可以返回退出状态列表(可以是信号名称或正整数),并在$status列表变量中可供调用者使用。

es将其扩展为能够返回任何内容的任何列表,并且不是使其在 中可用$status,而是使用语法获取退出状态(或函数返回值)<={...}

所以你可以这样做:

fn foo { return foo$1 }
echo <={foo bar}

例如。

但请注意,只有由空列表或元素全为空或 0 的列表组成的返回值才会被解释为成功的。因此,例如,这里foo anything && echo bar永远不会输出barfoo总是返回一个永远不会解释为的值成功

克什93

除此之外$(...)${ ...; }已经讨论过,还有一个功能允许扩展具有动态内容而不涉及 I/O:

学科

您可以定义一个在每次设置或扩展变量时调用的函数。对于关联数组变量,这些函数将有权访问下标,因此您可以使用它将一个任意参数传递给函数:

typeset -A valsub
function valsub.get {
  .sh.value=foo${.sh.subscript}
}
echo "${valsub[bar]}"

会输出foobar.

数学函数

ksh93 也有数学函数,但语法与以下函数不同zsh

function .sh.math.square x {((.sh.value = x*x))}
echo "$(( square(5) + square(12) ))"

答案2

回答实际提出的问题:没有。没有其他外壳有这个。

正如 MirBSD Korn shell 变更日志告诉我们的那样,价值替代由 Thomas Goirand 于 2014 年添加到版本 46。

据我所知,当时或此后没有其他 shell 复制过这个想法。他们中的一些人重视替代品源自,但它们实际上并没有价值替代。

答案3

${ cmds;}ksh93 的命令替换形式在同一 shell 中运行,cmds但以其他方式捕获标准输出,就像常规命令替换一样。例子:

a=1; echo ${ a=2; echo wtf;}; echo $a
wtf
2

它捕获命令的标准输出的事实是确切地它有何用处,因为您不必将输出保存到某个临时文件中,然后将其读回,或设置命名管道,或重写一些毛茸茸的函数以使其将其输出附加到某个变量,而不仅仅是写入出来。

这与“价值替代” mksh 功能非常不同,我无法找到任何理由。为什么不能REPLY先分配变量,然后将其用作$REPLY

相关内容