我一直认为 shell 会解析整个脚本,构建 AST,然后从内存中执行该 AST。但是,我刚刚阅读了 Stéphane Chazelas 的评论,并测试了执行此脚本 edit-while-executing.sh:
#!/bin/bash
echo start
sleep 10
然后在它睡觉时运行:
$ echo "echo end" >> edit-while-executing.sh
它的工作是让它在最后打印“结束”。
但是,在尝试修改时:
#!/bin/bash
while true; do
echo yes
done
通过做:
$ printf "%s" "no " | dd of=edit-while-executing.sh conv=notrunc seek=35 bs=1
它没有用,并一直打印“是”。
我也想知道其他非shell解释器是否也这样工作,并用python尝试了第一个脚本的等价物,但没有用。不过,也许 python 不再是解释器,而更像是一个 JIT 编译器。
所以重申我的问题,这是一种普遍存在于 shell 中并仅限于它们的行为,还是也存在于其他解释器(那些不被视为 shell 的解释器)中?另外,这是如何工作的,我可以做第一次修改而不是第二次修改吗?
此功能存在于其他提供所谓的 a 的解释器中
read
eval
print
loop
。LISP 是一种相当古老的语言,具有这样的功能,而 Common LISP 有一个read
函数,可以在此处读取表达式(+ 2 2)
,然后可以将其传递给eval
评估(尽管在实际代码中,出于各种安全原因,您可能不想这样做):我们还可以定义我们自己的非常简单的 REPL,而无需太多功能或调试或其他任何东西,但这确实显示了 REPL 部分:
基本上就像铭牌上说的那样,数据被读入、评估、打印,然后(假设没有发生任何崩溃并且仍然有电或为设备供电的东西)它循环回到读取不需要提前建立 AST。(出于显示原因,SBCL 需要
force-output
和fresh-line
添加,其他 Common LISP 实现可能会也可能不会。)REPL 的其他内容包括 TCL(“被放射性 LISP 咬住的外壳”),其中包括 Tk 的图形内容
或 FORTH 在这里定义一个函数
f>c
来进行温度转换(“ ok” 由添加gforth
):所以,这会在 Bash/dash/ksh/zsh 中无限期地运行(或者至少直到你的磁盘填满):
需要注意的是,只有在 shell 读取的最后一行之后添加到脚本文件的
内容才有意义。如果输入是管道,shell 不会返回重新读取较早的部分,他们甚至不能这样做。类似的构造在 Perl 中不起作用,它在运行之前读取整个文件。
我们可以看到,当通过管道输入时,它也会这样做。这会在 1 秒后给出一个语法错误(且仅此错误):
当相同的脚本通过管道传输到 Bash 时,打印
hello
,然后在一秒钟后抛出语法错误。Python 看起来类似于具有管道输入的 Perl,即使解释器在交互时运行读取-评估-打印循环。
除了逐行读取输入脚本之外,至少 Bash 和 dash
eval
一次一行地处理参数:Zsh 和 ksh 立即给出错误。
与源脚本类似,这次 Zsh 也像 Bash 和 dash 一样逐行运行:
至少有一个壳,鱼,没有表现出这种行为(但鱼在其他方面是不寻常的):
(此答案的先前版本对 Python 和 Ruby 的观察有误。)