Bash 为什么要发明 shopt 命令

在 Bash 中,有两个内置命令用来控制 Bash 的各类可配置行为的开关(打开或关闭),这些开关称之为选项(option)。其中一个命令是 set,set 命令有三种功能:显示全部的变量和函数;修改 Bash 的位置参数;控制 Bash 的第一套选项。可见 set 命令彻底违背了“一个命令只干一件事”的 UNIX 哲学。另一个命令是 shopt,从名字(shell options 的缩写)就能够看出,它的功能是控制 Bash 的另外一套选项。那么问题就来了,为啥要用两套选项?html

在回答为何以前,咱们先看看二者的不一样点:shell

1. set 命令是 POSIX 规范,shopt 不是

set 命令是 Bash 从 sh 继承来的,并且它和它的大多数选项一块儿都是在 POSIX 规范中的。而 shopt 是 Bash 在 2.0 版本时新增的,别的 Shell 没有这个命令。bash

$ set -o | wc -l函数

27spa

$ shopt | wc -lhtm

47继承

在我电脑上的 Bash 4.4 beta 中,set 一共有 27 个选项,shopt 一共有 47 个选项。进程

2. set 命令和 shopt 命令分别对应两个不一样的环境变量

在 Bash 1.* 时代,用 set 命令开启的选项只能在当前 Shell 进程中生效,没有办法经过环境变量传递给它的子进程 Shell,从 Bash 2.0 开始,新增了一个只读变量 SHELLOPTS,只要把它设置成环境变量,它就能把在当前 Shell 中打开的选项传递给子进程 Shell。ip

$ echo $SHELLOPTS字符串

braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor

$ set -o noglob

$ echo $SHELLOPTS

braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor:noglob

$ echo *

*

$ export SHELLOPTS

$ bash -c 'echo *'

*

上面的例子演示了:在当前 Shell 中打开了 noglob 选项,而后 SHELLOPTS 变量的值会自动同步(全部开启的选项名用冒号 join 成的字符串),但这个变量默认并非环境变量,须要手动 export 一下,而后子进程 Shell 会获取到这个环境变量的值,解析以后,打开这些继承来的选项。为了演示 Bash 的确有这个解析过程,能够这么玩:

$ env SHELLOPTS=foo bash

bash: foo: invalid option name

值得注意的是,虽然 shopt 命令和 SHELLOPTS 变量是同时实现的(Bash 2.0),并且它俩的名字看起来也的确像是有对应关系似的,然而并无。shopt 命令一直没有一个像 set 命令之于 SHELLOPTS 的东西,直到 Bash 4.1,才有了 BASHOPTS 变量,它的功能和 SHELLOPTS 同样,用来把 shopt 命令打开的选项传递给子进程 Shell,这里就不具体演示了。

3. shopt 也能够控制 set 的选项,反之则不行

shopt 命令有个 -o 选项,这个选项的功能就是用来查看或修改本来用 set 控制的那套选项,好比咱们随便选个 set 的选项 noglob:

$ shopt -s noglob

bash: shopt: noglob: invalid shell option name

$ shopt -so noglob

$ shopt noglob

bash: shopt: noglob: invalid shell option name

$ shopt -o noglob

noglob         on

不加 -o 控制本身的一套选项,加上 -o 控制 set 控制的那套选项。可见在控制 Bash 的选项这个功能上,shopt 命令彻底能够代替 set 命令。

4. 为何要发明 shopt

在了解了这两个命令以后,我不由要问:为何要发明一个新的命令?要知道,清楚的记住哪一个选项属于哪一个命令是很难的,好比我问你 noglob 和 nullglob 哪一个是 set 选项哪一个是 shopt 选项,没几我的能记得。为何不像 zsh 同样让 set 管理全部的选项呢:

$ zsh -c 'set -o | wc -l'

176

我本身猜想了好久:是否是 set 命令的短选项不够了?但我又看到不是全部的 set 长选项都有对应的短选项。是否是 Bash 做者在当时决定之后把 POSIX 规定的选项放一个地方,把其它 Bash 私有的选项放另外一个地方,何况 set 命令已经很复杂了,因此发明了个新命令?而后我又发现不少 set 的选项都不在 POSIX 规范里,好比 onecmd、pipefail、history 和 errtrace 等。因为这些猜想说服不了个人好奇心,因而我在 help-bash 上询问了 Bash 做者,毕竟这是 20 年前的事了,除了他谁还可能知道 http://lists.gnu.org/archive/html/help-bash/2015-10/msg00008.html

在邮件里,我咨询了两个问题,一个就是“为何不让 set 控制全部的选项,为何要发明 shopt”;另一个是“给 shopt -o 参数是否是意味着 Bash 的实现者鼓励人们用新的 shopt 命令而不是旧的 set 命令来控制 Bash 选项”。

第一个问题的答案比较复杂,总结一下就是:做者的出发点的确是为了让 set 控制“那些在 POSIX 规范里的选项”,以及“那些从 sh 继承来的,但不在 POSIX 规范里的选项(好比上面提到的 onecmd)”,以及“那些为了兼容性,从 ksh 引入的,但不在 POSIX 规范里的选项(好比上面提到的 pipefail)”;让 shopt 控制那些 Bash 私有的选项。但因为历史缘由,20 年之后,如今看来,这两个出发点显得都不是那么有说服力:如今的 set 选项里存在着既不是从 sh 继承的,又不是从 ksh 学来的,又不在 POSIX 规范中的选项,好比 history 和 errtrace 等,Bash 做者解释说,history 是他但愿 POSIX 规范能采纳(然而目前并无),因此他先实如今了 Bash 里, errtrace(-E) 选项是由于他为了和 errexit(-e)对应起来,因此实现了,他还说若是再来一次的话,他会把 errtrace 放在 shopt 里。至于 shopt 里放着的选项是否是都是 Bash 私有的,也并非,ksh 和 zsh 也从这些选项里引入了一些到本身的 set 选项里。除了上面我提到名字的选项,邮件里还讲了另一些不符合通常规律的选项,很复杂,看了也记不住。总之,两个命令的两套选项显得杂乱无章,毫无规律,是历史缘由。读到这里,也许有些好奇心强的朋友还想问:难道把 Bash 的私有选项也放 set 里不行吗,不行吗,不行吗!是行,这只是 Bash 做者在当时作的一个决定,要分开放,没什么特殊的缘由,这样说应该说服你了吧。

第二个问题没有回答我,我就再也不追问了,我猜答案是确定的,不然干吗实现那个功能。 

相关文章
相关标签/搜索