[译]macOS Bash升级
许多macOS用户不知道自己使用的是过时已久的Bash shell版本。强烈建议升级到最新版本。
macOS默认Bash版本
执行以下命令,查看macOS的Bash版本是否过时:
$ bash --version
GNU bash,版本3.2.57(1)-release(x86_64-apple-darwin18)
版权所有(C)2007 Free Software Foundation,Inc。
正如所见,这是GNU Bash 3.2版本,可以追溯到2007年!这个版本的Bash内置在所有版本的macOS,即使是最新版本。
Apple在其操作系统中内置如此旧版本的Bash的原因与许可协议有关。从4.0版本(3.2的后续版本)开始,Bash使用了GNU通用公共许可证v3(GPLv3),但Apple并不(想要)支持。你可以在这里找到一些相关讨论。GNU Bash3.2版本是Apple可接受的许可协议的最后一个版本,因此始终内置该版本。
这意味着整个世界(例如Linux)都会使用新版本Bash,而macOS用户却使用十年前的旧版本。在撰写本文时,GNU Bash的最新版本是5.0(见此处),已于2019年1月发布。在本文中,我将说明如何将系统默认shell升级到最新版本的Bash。
为什么升级?
如果Bash 3.2可以正常使用,为什么还要升级更新的版本呢?对我个人而言,主要原因是自动参数补全。该功能支持Bash特定命令的自动参数补全。您可以输入部分内容,然后按Tab
键,自动补全命令,文件名或变量。(或者当有多个可选项时,双击Tab
键来获取可补全信息的列表)。这是Bash的默认内置补全功能。
不仅于此,它还支持依赖上下文场景判断的命令补全。例如输入 cmd -[tab][tab]
,然后查看适合该命令的所有选项列表。或者输入 cmd host rm [tab][tab]
然后查看相关配置文件中指定的所有“host”的列表。
可编程完成逻辑(由命令的创建者)在完成规范中定义,通常以完成脚本的形式。必须在shell中提供这些完成脚本以启用命令的完成功能。
自动参数补全逻辑(由命令的开发者)在自动完成
规范中定义,一般称为 completion scripts
。必须在shell中提供这些源文件才能生效。
问题是Bash的自动命令补全功能自版本3.2以来已经被广泛应用,大多数命令都支持这个新功能。但在Bash 3.2上无法使用,如果继续使用默认的 macOS shell ,则会错过许多命令的补全功能。
通过升级到更新版本的Bash,您就可以使用这些非常有用的自动补全脚本。我写了一篇文章,名为 Programmable Completion for Bash on macOS ,它解释了在升级到更新的Bash版本后,充分利用macOS上的自动命令补全功能所需的全部内容。
如何升级?
升级macOS的默认shell到最新版本Bash,需要做三件事:
- 安装最新版本Bash
- 设置“白名单”,将新Bash作为登录shell
- 设置新Bash为默认shell
每个步骤都非常简单。
注意:以下步骤并未更改旧版Bash,而是安装新版并将其设置为默认。两个版本将并存,只是我们忽略旧版。
安装
推荐使用 Homebrew:
brew install bash
验证安装结果, 检查系统中并存的2个版本:
$ which -a bash
/usr/local/bin/bash
/bin/bash
第一个是新版,第二个是旧版:
$ /usr/local/bin/bash --version
GNU bash, version 5.0.0(1)-release (x86_64-apple-darwin18.2.0)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ /bin/bash --version
GNU bash, version 3.2.57(1)-release (x86_64-apple-darwin18)
Copyright (C) 2007 Free Software Foundation, Inc.
在PATH
变量中,新版本(/usr/local/bin
)的目录优先于旧版本(/bin
),所以现在再检测Bash版本时就显示已是新版:
$ bash --version
GNU bash,版本5.0.0(1)-release(x86_64-apple-darwin18.2.0)
...
现在已经设置好了默认新版。
设置白名单
UNIX包含一个安全功能,该功能将可用作登录shell的shell(即登录到系统后使用的shell)限制为“受信任”shell列表。这些shell列在/etc/shells
文件。
要将新安装的 Bash shell 设置成默认shell,它必须能够作为登录shell。将其添加到/etc/shells
文件。以root用户身份编辑此文件:
$ sudo vim /etc/shells
添加/usr/local/bin/bashshell
,如下:
/bin/bash
/bin/csh
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh
/usr/local/bin/bash
设置默认Shell
现在如果打开一个新终端窗口,仍然会使用Bash 3.2。这是因为/bin/bash
仍然是默认shell。修改成新shell,执行以下命令:
$ chsh -s /usr/local/bin/bash
现在默认shell已设置为新版本Bash。关闭重启终端窗口,启用新版本。继续验证:
$ echo $BASH_VERSION
5.0.0(1)-release
chsh
命令仅修改当前用户的默认shell。如果想修改root用户,执行以下操作:
$ sudo chsh -s /usr/local/bin/bash
这样当使用 sudo su
,以root用户身份打开shell时,也将使用新版Bash。
注意事项
脚本中的用法
如上所述,我们没有修改Bash的默认版本,而是安装了新版并将其设置为默认。Bash的两个版本并存:
-
/bin/bash
:旧版 -
/usr/local/bin/bash
:新版
在shell脚本中,我们经常在开头写一行Shebang,如下:
#!/bin/bash
echo $BASH_VERSION
注意,这一行Shebang明确指定了旧版本Bash。运行时就会调用旧版本的Bash执行(脚本输出显示,例如3.2.57(1)-release
)。
大多数情况下,这可能没问题。如果想明确使用新版本Bash,修改shebang如下:
#!/usr/local/bin/bash
echo $BASH_VERSION
现在输出类似5.0.0(1)-release
。但要注意,该方案不可移植,可能无法在其他系统上运行。因为其他系统可能没有位于/usr/local/bin/bash
的shell(而/bin/bash
基本是标准)。
要两全其美,修改shebang如下:
#!/usr/bin/env bash
echo $BASH_VERSION
这是shebang的推荐格式。原理是检查PATH
并使用第一个找到的bash可执行文件作为脚本的解释器。如果新版的目录先于旧版(是默认版本),则将使用新版,并且脚本的输出类似5.0.0(1)-release
。
为什么不使用Symlink符号链接?
与其并存两个版本的Bash,为什么不能彻底删除旧版,替换成新版?例如:
$ sudo rm /bin/bash
$ sudo ln /usr/local/bin/bash /bin/bash
这样即使是Shebang标记成#!/bin/bash
的脚本,也会被新版Bash执行,Why not?
当然可以,但是必须绕过macOS的安全设置系统完整性保护(SIP)
(维基百科)。该功能禁止对特定文件夹进行写操作,即使是root用户也不行(所以也称“rootless”)。这些文件夹列在这里,其中就包括了/bin
。
解决方案是禁用SIP
,修改/bin
,再启用SIP
。启用和禁用SIP
的方法在此。启动计算机到Recovery模式,然后使用 csrutil disable
和 csrutil enable
命令。
至于是彻底使用新版替换旧版,还是并存2个版本,自行判断吧。
参考:https://apple.stackexchange.com/a/292760
Q:系统终端窗口没有切换成新版Bash?
A:两种可能:
- 杀终端进程并重启。
- 系统终端窗口菜单栏,“偏好设置”>“通用”>“Shell的打开方式”,确保选中“
默认登录shell
”。如果选中其他选项并设置为/bin/bash
,就会强制使用旧版。