Ash (Almquist Shell) 变体研究
Bourne | Ash | #! | find | ARG_MAX
| Shells | whatshell | portability | permissions | UUOC | ancient | - | ../Various | HOME "$@
" | echo/printf | set -e | test | tty defs | tty chars | $()
vs )
| IFS | using siginfo | nanosleep | line charset | locale
Ash (Almquist Shell) 变体研究
2006-02-14 .. 2021-02-13 (参见最近的更改)
在多次思考了这些 "ash" 变体之间的关系,并且几乎没有找到任何相关信息之后,我对已知的一些变体进行了更深入的研究。
除了 BSD/OS 之外,所有变体都有源代码可用。 感谢 TUHS 存档了传统的 BSD 和 386BSD,感谢 Kirk McKusick 的 CSRG archive,以及 Peter Seebach 允许我学习所有 BSD/OS 变体。
本文档记录了这些变体之间的关系。对于那些没有变更日志的变体(传统的 BSD、386BSD、BSD/OS 和 Minix),本文档旨在提供关于源代码更改的 完整 日志。但是,对于其他的、较新的变体,这显然不是目标,只列出了一些任意选择的更改或错误修复。为什么没有注释的差异?我无法完成这项工作。如果您对这个级别感兴趣,那么您已经在查看源代码,并且这些注释是一个好的开始。
内容:
· 1.) 原始版本 '89 · 2.) 传统 BSD: 4.3BSD-Net/2 '91, 4.4BSD-Alpha '92, 4.4BSD '93, 4.4BSD-Lite '94, 4.4BSD-Lite2 '95 · 3.) 386BSD '92-'93 · 4.) BSD/OS (BSDi) '91-'03 · 5.) NetBSD '93- · 6.) FreeBSD '96- · 7.) 从 NetBSD 到 Linux 的早期移植 '93 (以及早期的 Slackware、Debian) · 8.) dash '97- (以及后来的 Slackware) · 9.) dash 的 Slackware 变体 '06- · 10.) Android 变体 (源自 NetBSD) '05- · 11.) ash 的 Cygwin 变体 '98- · 12.) BusyBox '01- · 13.) Minix '01-
1.) 原始版本 ('89) - sh(1)
它由 Kenneth Almquist 编写,作为传统 "System V Release 4" Bourne shell 的替代品,原因是 AT&T 和 Berkeley 之间的许可证战争。Berkeley 首先在 "BSD 4.3-Net/2" 中发布了它。源代码发布到了 comp.sources.unix, "A reimplementation of the System V shell",发布于 1989 年 5 月 30 日。此后不久,Kenneth 发布了一个 job control patch,以解决无法正确处理超过 4 个作业的问题。
首先,是一些概述,以便了解这些 almquist shells 的相关信息。
ash 家族和 SVR4 shell 之间的差异:
- ash 实现了 "
$(...)
" 命令替换 - 接受 "
export VAR=value
" echo
接受选项 -e 来解释转义序列(未文档化)- 原始发行版中的文件 DIFFERENCES 列出了与 Berkeley shell(V7 shell 的一个变体)和 SVR4 shell 的大多数剩余差异。一些差异已被记录但未在此文件中列出,例如,内置的 "
bltin
" 和 "setvar
" - 仅在手册中提到,但未解释:内置的
"catf"
,"expr"
,"line"
,"nlecho"
- almquist shells 的一个典型特征:以 "
**%func**
" 结尾的 PATH 组件被识别为包含函数定义的目录。阅读更多关于此的信息。 - almquist shells 的另一个典型特征:
"local -"
使$-
在函数中是本地的 (感谢 Jilles Tjoelker) - 实现了 "
--
" 作为内置命令的选项结束符 - "自然地",特定 Bourne shell 行为列表中的任何内容都没有实现。
ash 变体和 SVR4 shell 独有,与其他 bourne 兼容 shell 相比:
- "
notexistent_cmd 2>/dev/null
" 不会重定向 "not found" 错误(例外:自 0.4.17 以来的 dash 和 FreeBSD 9.0) - "
echo x > file*
" 不会展开file*
仅在早期 ash 变体中找到的细节:
- (仅)原始版本知道一个未文档化的版本变量:
SHELLVERS="ash 0.2"
。后来的变体只能通过特征来识别和区分。(后来的 linux 移植版本 "ash-0.2" 与此变量无关。) - 像 SVR4 sh 一样:没有命令行编辑和历史记录 (故意的)
- 像 SVR4 sh 一样:没有
#
和%
形式的参数展开 - 像 SVR4 sh 一样:没有算术扩展
- 像 SVR4 sh 一样:没有像
${10}
这样的多位参数扩展 - 像 SVR4 sh 一样:"
set --
" 不会取消设置参数列表 - 像 SVR4 sh 一样:关于
IFS vs. "$*"
的传统行为 - 故意地,在反引号形式的命令替换 (
...
) 中没有反斜杠转义,副作用:这种形式不能嵌套 [在 4.4BSD 中更改] - 未文档化:没有 "type" 内置命令 (可能是偶然的) [在 NetBSD1.3, FreeBSD2.3 中修复]
- **默认情况下,read 的行为就像 POSIX flag -r 处于活动状态
- PS1 是 "
@
" - 使用 "
sh -c cmd arg0 arg1
",$1
设置为arg0
[修复得相对较晚:所有传统的 BSD 以及早期的 Free-/NetBSDs 和早期的 Minix 的行为都是如此] - bug: "
VAR=set eval 'echo $VAR'
" 没有输出,也就是说,您不能使用局部变量调用eval
- "
for i do
" 不被接受 (但 "for i; do
" 或 "for i<newline>do
") - 未实现标志
-v
- 启动时读取 SHINIT(除非是登录 shell 或使用 "sh file" 调用)
- "
trap
" 不接受符号信号名称 - "
expr
" 内置命令,与 "test" 合并 - 使用 "
!!pattern
" 对模式匹配进行否定 - 标志
-z
(如果文件名模式不匹配,则不折叠) - 拒绝 "
case x in (x)
" [1] [很快修复。例外情况是,NetBSD 和 Minix 很晚才修复此问题] - bug: 空命令替换 "
$( )
" 上的段错误 (但不是在 " - bug: "
${var=
cmd}
" 上的段错误 - 编译时选项(默认情况下关闭)用于 "cleaner" eval(但与上述问题无关)
- 如果内核失败,shell 识别
#!
(如果未编译 BSD 支持)。此编译时宏解释为 "running 4.2 BSD or later"。此代码仍然存在于某些现代变体中,但实际上已失效。
[1] | 事实上,没有 ash 变体 需要 "case x in (x)
",因为解析器对 $(...)
命令替换中的 case 构造是健壮的。但是,其他几个 shell 不够健壮,他们必须以这种方式解决;所以这是一个脚本可移植性问题。它迟早会在大多数实现中得到修复:BSD/OS 3.0 (06/'96), dash-0.3.5-4 (08/'99) (因此 Busybox 和 Slackware 8.1 ff.), FreeBSD 4.8/5.1 (10/'02), NetBSD 4.0 (06/'06)。
---|---
早期和一些后来的 ash 变体中发现的细节:
- bug: 命令替换中的内置命令被错误地优化,shell 在
foo=
exit 1`` 时退出 [在 NetBSD 1.4, dash-0.3.5 以及因此 busybox, FreeBSD 9.0 中修复]
在 ash、SVR4 shell 和大多数 bourne 兼容 shell 中发现的一个细节,但通常没有文档记录:
- 接受 "
{ ...; }
" 作为for
循环中的主体(而不是 "do ...; done
")
2.) 传统 BSD ('91-'95)
4.3BSD-Net/2 (06/'91) - sh(1)
- PS1 取决于 uid (众所周知的
#
或$
) - 与原始发行版相反,在 BSD 上,
echo
默认情况下不解释转义序列,但需要-e
- 不仅 "--",而且 "-" 也终止选项
- shell 版本变量
SHELLVERS="ash 0.2"
已删除 - 未文档化:接受标志 -v(最初省略),但未实现
- 用于在 eval 中进行不同解析的编译时选项已删除
- 应用了上述 job control patch。
- 内置的 "bltin" 重命名为 "command" (直到下一个版本才记录)
- 内置的 "catf", "expr", "line", "nlecho" 已删除
- "chdir" 成为 "cd" 的同义词
- 没有 HOME 的用户从 /tmp 而不是 / 开始 (除了 uid 0)。
4.4BSD-Alpha (06/'92) - 开始瞄准 POSIX.2 - sh(1)
- 添加了历史记录和行编辑 (fc, $FCEDIT, $HISTSIZE, flags -E 和 -V),并删除了 atty 支持 (通过终端驱动程序的可选支持)
- 算术扩展 "
$((...))
" - ...以及未文档化的内置命令
"let"
和"exp"
- 内置命令 "printf"
- 用 ! 否定管道
- 添加了 tilde 扩展,删除了 /u/logname 机制
- 别名
- "set -o" 打印选项
- 添加了 "cd -"
- 启动时读取
ENV
而不是SHINIT
- 将标志 -j (jobcontrol) 重命名为 -m (如 SVR4 shell),添加了标志 -C (noclobber), -a (allexport), -b (notify asynchronous jobs),实现了已记录的标志 -v (以前接受但未实现),删除了标志 -z,(如果文件名模式不匹配,则不折叠) 接受了(文档记录为未实现)标志 -u (error on unset)
- 文档记录了多位参数替换,例如
${10}
,但未实现它 - 警告 "You have stopped jobs."
- "
shift
" 内置命令知道 "can't shift that many" - 在这种情况下,警告 "running as root with dot in PATH"
- 删除了使用 "
!!pattern
" 对模式匹配进行否定 - bugfix: 接受空命令替换 "
$( )
" - 内置命令 "
lc
" 已删除 - 手册页已完全重写
- ... 并且
%func/%builtin
功能已从手册中删除,但未从代码中删除 - ... 并且请注意,
#
和%
形式的参数扩展被记录为未实现
4.4BSD (06/'93) - sh(1)
显然,386BSD patchkit 2.4 (本地副本) 进入此版本或反之亦然:
- 修复了反引号命令替换 (
...
) 中的转义,因此反引号变为可嵌套的 - 接受 "for i do"
- 反引号命令设置 $?
- 如果先前已重定向,则不要将后台命令的 STDIN 重定向到 /dev/null
- 有关在带引号的变量和 case switch 中转义的 bugfix
- 重定向可能在非简单命令之前
和:
- "wait" 产生退出状态
- "fc" 知道 "missing history argument"
- 扩展的版权消息和所有文件中的 sccsid
4.4BSD-Lite (06/'94) - sh(1)
- 如果调用时不带参数,"read" 会因 "arg count" 错误而失败。 该代码已经在那里,但一个错误仅导致 SEGV。
- 条件参数扩展中的 bugfix,例如接受 "
${var=
cmd}
"
4.4BSD-Lite2 (06/'95) - sh(1)
此版本已在 05/'95 左右与 NetBSD 同步。
- 参数扩展知道 # 和 %
- 添加了 "ulimit" 内置命令
- 删除了 "printf" 内置命令
- 算术扩展接受二进制逻辑运算符 (&, |, ^, ~)
- "umask" 理解符号表示法
- 命令之前的 PATH 分配甚至被视为查找
- source 命令为可执行文件执行路径查找
- 修复了有关保存的退出状态(示例:"
false; echo
echo $?``" 或 "false || foo=bar; echo $?
" 或 "foo=; false; $foo; echo $?
") - 删除了默认 PATH 中的前导冒号(影响受限模式)
- 如果 PATH 包含点,则不再警告 root
- 仅当 ID 等于有效 ID 时,才在启动时查找 ENV
- 添加了 "false" 内置命令 ("true" 已经与 ":" 的处理方式相同)
- 可以在范围内转义 dash,例如 [a\-z]
3.) 386BSD ('92-'93) - sh(1)
386BSD 发行版源自 4.3BSD-Net/2,旨在维护一个可运行的系统,也就是说,使用在许可战争后必须删除的部分来完成 Net2。
-
386BSD 0.0:初始版本,sh 与 4.3BSD-Net/2 相同 (02/'92)
-
386BSD 0.1: "cd" 不打印目标目录 (CDPATH, 符号链接)
-
patchkit 0.2.3 (04/'93):修复了 "cd" (在内部调用 pwd(1) 时)
-
patchkit 0.2.4 (06/'93) (本地副本)
- 修复了反引号中的转义,因此反引号变为可嵌套的
- 接受 "for i do"
- 反引号命令设置 $?
- 如果先前已重定向,则不要将后台命令的 STDIN 重定向到 /dev/null
- 有关在带引号的变量和 case switch 中转义的 bugfix
- 重定向可能在非简单命令之前
请参阅 TUHS 上的 386BSD 存档。
4.) (BSDi) BSD/OS ('92-'03)
这是一个来自供应商 BSDi 的商业 BSD 分支。 例如,供应商 F5 使用 BSD/OS 4.1 作为其产品 BIG-IP 4.2 的核心。
-
初始版本于 01/'92 从 4.3BSD-Net/2 获取。 经过一些微小的更改,这成为 BSD/OS 1.0 (03/'93)。
-
BSD/OS 1.1 (01/'94)
- 与 4.4BSD 重新同步,并且
- 添加了类似于 csh 的 "limit/unlimit"
- 添加了受限功能
- 改进了 "case" (POSIX:不要识别模式中的关键字)
- 修复了重定向中的错误
- 修复了 "set -a" 的 bugfix
- 非交互式 shell 在 cmd 终止之前不处理 SIGINT
-
BSD/OS 2.0 (02/'95)
- 与 4.4BSD-Lite 重新同步,并重新添加了早期的 BSD/OS 特定更改
- 使用 POSIX.2 规则获取 IFS 分隔符
- POSIX 使 ":" 成为特殊的内置命令,现在它在错误时的行为与 "true" 不同
- 修复了在特殊条件下 "unset" 的返回值
- 修复了受限模式:除了 /cmd 之外,还拒绝 ./cmd
- 修复了资源消耗输出中的内部类型(现在为 unsigned)
-
BSD/OS 2.1 (03/'95)
- "fc" 历史记录内置命令的微小修复 (NULL 参数)
-
BSD/OS 3.0 (06/'96)
- 与 4.4BSD-Lite2 重新同步,并重新添加了早期的 BSD/OS 特定更改
- ...因此 "printf" 内置命令再次消失
- (例外:main.c 是 8.7 7/19/'95",而不是 44BSD-Lite2 中的 "8.6 5/28/'95")
- 添加了重定向 "<>"
- 接受 "case x in (x)"
- 修复了有关匹配反斜杠转义点的错误
- 修复了有关取消引用空指针的一些错误
- "showtree" 输出的 bugfixes (编译时调试选项)
-
BSD/OS 4.0 (02/'98) - 和 BIG-IP 4.2 (BSD/OS 4.1 派生,供应商为 F5)
- 添加了 "test" 内置命令 (链接到系统源)
- 修复了 "getopts" (OPTIND 变量,错误打印脚本名称而不是 "getopts")
- 修复了 "return" 的退出状态(内置命令)
- 修复了重定向代码 (关闭文件描述符)
- 修复了受限模式
- 删除了未使用的 #! 支持
-
BSD/OS 5.0 (05/'02)
- 修复了 "cd" (在内部调用 pwd(1) 时)
-
BSD/OS 5.1 (05/'03)
- 添加了标志 -r (与 POSIX 同步); 以前的 "read" 标志 -e 成为默认值
因此,例如,与现代变体仍然存在以下差异:
- 没有 "type" 内置命令
- "trap" 不接受符号名称
- 关于 IFS vs. "$*" 的旧行为
5.) NetBSD ('93-current)
最初源自 386BSD-0.1 patchlevel 0.2.3,很快与 4.4BSD-Lite 同步。
一些变化:
- 最初为 386bsd-0.1 patchkit 0.2.3, 03/'93, NetBSD 0.8
- 386BSD patchkit 0.2.4 07/'93, NetBSD 0.9
- "false" 内置命令, 07/'93, NetBSD 0.9
- "umask" 接受符号表示法, 07/'93, NetBSD 1.0
- 与 4.4BSD-Lite 同步 , 05/'94, NetBSD 1.0
- "test" (系统命令,尚未内置) 从 pdksh-5.0 导入, 06/94, NetBSD 1.0
- "ulimit" 内置命令, 11/'94, NetBSD 1.2
- # 和 % 参数扩展 , 01/'95, NetBSD 1.2
- 使用 "sh -c cmd arg0 arg1", $1 设置为 arg1 , 10/'96, NetBSD 1.2
- 错误地接受没有大括号的多位参数扩展,例如
$10
, 01/'97, NetBSD 1.3 - 添加了 "type" 内置命令 , 02/'97, NetBSD 1.3
- 关于 IFS vs. "$*" 的现代行为, 07/'97 for NetBSD 1.3
- "read -r", 11/'97, NetBSD 1.3 (以前默认行为如此)
- "
<>
" 重定向, 02/'99 - "test" 变为内置命令 (链接系统命令源), 04/'00, NetBSD 1.5
- "trap" 接受符号名称 (以及更多修复), 03/'01, NetBSD 1.6
- "set -u" 实际实现, 03/'02, NetBSD 1.6
- 添加了 "printf" 内置命令 , 11/'02, NetBSD 2.0
- 算术扩展接受没有美元符号的变量名, 03/'05, NetBSD 3.1/4.0
- 接受 "
case x in (x)
" [[1] 请参见上面的脚注], 06/'06, NetBSD 4
NetBSD 0.8 和 0.9 (作者 Jason Steven aka Neozeed)。 netbsd.org 上的 CVS-Web (自 ~1.0 起)。
6.) FreeBSD ('94-current)
源自 4.4BSD-Lite, -Lite2 和 NetBSD 1.2/1.3
一些变化:
- 从 4.4BSD-Lite 05/'94 的初始导入
- 使用 "sh -c cmd arg0 arg1", $1 设置为 arg1, 10/'95, FreeBSD 2.1
- 合并来自 4.4BSD-Lite2 的源 , 05/'96+10/'96, for FreeBSD 2.2
- 合并 NetBSD (~1.2) 源 , 12/'96, for FreeBSD 2.2
- "trap" 接受符号名称, 12/'96, FreeBSD 2.2
- 添加了 "type" 内置命令 (来自 NetBSD) , 04/'97, FreeBSD 2.2.5
- [^...] 否定, 6/'97, FreeBSD 2.2.5
- 从 NetBSD ~1.3 导入 expand.c , 04/'97, ~FreeBSD 3
- 关于 IFS vs. "$*" 的现代行为, 09/'98, FreeBSD 3.1
- "test" (系统命令,尚未内置) 从 pdksh 导入, 08/99, FreeBSD 3.3
- "read -r", 09/'99, FreeBSD 3.3 (以前默认行为如此)
- "VAR=set eval 'echo $VAR'" 打印 "set" 而不是 nothing, 05/'00, FreeBSD 4.6/5.0
- "
<>
" 重定向, 10/'00,03/'01, FreeBSD 4.6 - "test" 变为内置命令 (链接系统命令源), 11/'01, FreeBSD 4.9/5.0
- "set -u" 实际实现 (请参阅 NetBSD), 03/'02, FreeBSD 4.7
- 接受 "
case x in (x)
" [[1] 请参见上面的脚注], 08-09/'02, FreeBSD 4.8/5.1 - 算术扩展接受没有美元符号的变量名, 08/'03, 5.2
- 删除了
PATH
中%builtin
的特殊处理(%func
仍然存在), 9.0 - "
notexistent_cmd 2>/dev/null
" 现在还重定向错误 "not found", 9.0 - shell 不再在
foo=
exit 1`` 时退出, 9.0
CVS-Web 在 freebsd.org 上。
7.) 将 NetBSD 变体早期移植到 Linux ('93)
贡献者包括 Sunando Sen, Arjan de Vet, Florian La Roche。 此早期移植与 Herbert Xu 的后来的 dash 无关。
- v0.0 08/93:导入 [NetBSD sh 变体 19930810](https://www.in-ulm.de/~mascheck/various/ash/<