在 Bash 中,有兩個內置命令用來控制 Bash 的各類可配置行爲的開關(打開或關閉),這些開關稱之爲選項(option)。其中一個命令是 set,set 命令有三種功能:顯示全部的變量和函數;修改 Bash 的位置參數;控制 Bash 的第一套選項。可見 set 命令徹底違背了「一個命令只幹一件事」的 UNIX 哲學。另一個命令是 shopt,從名字(shell options 的縮寫)就能夠看出,它的功能是控制 Bash 的另外一套選項。那麼問題就來了,爲啥要用兩套選項?html
在回答爲何以前,咱們先看看二者的不一樣點:shell
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 個選項。進程
在 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,這裏就不具體演示了。
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 命令。
在瞭解了這兩個命令以後,我不由要問:爲何要發明一個新的命令?要知道,清楚的記住哪一個選項屬於哪一個命令是很難的,好比我問你 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 做者在當時作的一個決定,要分開放,沒什麼特殊的緣由,這樣說應該說服你了吧。
第二個問題沒有回答我,我就再也不追問了,我猜答案是確定的,不然幹嗎實現那個功能。