Post

macOS 使用 GNU 命令

macOS 的自带命令行工具是 BSD 版本的,要想在 Mac 上开发可以完美运行在 GNU/Linux 上的 Shell 脚本,就必须依赖 Linux 服务器,或者本地 Linux VM / Docker 去测试脚本,甚是麻烦。如果将命令行工具从 BSD 版本替换为 GNU 版本,把 Mac 当做 Linux 来用(不包括内核部分),将会意义非凡。本文将会介绍替换的原理与方法。

安装 GNU 工具

所需的 GNU 工具可通过 Homebrew 安装,常用工具的安装如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ brew install coreutils
$ brew install findutils
$ brew install gnu-sed
$ brew install gnu-indent
$ brew install gnu-tar
$ brew install gnu-which
$ brew install gnutls
$ brew install grep
$ brew install gzip
$ brew install screen
$ brew install watch
$ brew install wdiff --with-gettext
$ brew install wget
$ brew install less
$ brew install unzip

如需要搜索所有 GNU 工具列表,可以通过以下命令获得:

1
$ brew search gnu

覆盖系统自带命令

Homebrew 安装的命令工具默认放置在 /usr/local/opt/,而系统自带 BSD 工具的路径为 /usr/bin/。当安装的 GNU 命令与系统自带命令重复时,用前缀 g 可以指定使用 GNU 版本,如:

1
2
3
$ gsed    # 使用 GNU 版本的 sed (gnu-sed)

$ sed     # 使用 BSD 版的 sed

如果想省去 g 前缀,在环境变量 PATH 中把 GNU 工具的执行路径放置于 /usr/bin 之前即可(在安装命令工具的时候,输出日志就有指示)。原理是在系统扫描可执行路径时,会使用第一个符合条件的值:

1
export PATH="/usr/local/opt/<PACKAGE>/libexec/gnubin:$PATH"

上述 PACKAGE 为工具包名称。对应上文「安装 GNU 命令工具」里提及的工具,在文件 ~/.zshrc (若使用 Bash,则为 ~/.bash_profile)添加以下指令以实现对系统默认工具的覆盖:

1
2
3
4
5
6
7
8
9
export PATH="/usr/local/opt/coreutils/libexec/gnubin:$PATH"
export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH"
export PATH="/usr/local/opt/binutils/bin:$PATH"
export PATH="/usr/local/opt/ed/libexec/gnubin:$PATH"
export PATH="/usr/local/opt/findutils/libexec/gnubin:$PATH"
export PATH="/usr/local/opt/gnu-indent/libexec/gnubin:$PATH"
export PATH="/usr/local/opt/gnu-tar/libexec/gnubin:$PATH"
export PATH="/usr/local/opt/gnu-which/libexec/gnubin:$PATH"
export PATH="/usr/local/opt/grep/libexec/gnubin:$PATH"

刷新使其生效:

1
$ source ~/.zshrc

在 BSD 与 GNU 之间切换

如果你因为临时业务需求,或者觉得花大价钱买来的 Mac 不用太浪费,想换回 BSD 环境时,那么可以暂时屏蔽相关的 GNU 的执行路径:

1
# export PATH="/usr/local/opt/<PACKAGE>/libexec/gnubin:$PATH"

聪明的你也许发现了,上述例子要手动屏蔽的内容多达 9 行,而且之后恢复 GNU 还得一条条去掉注释符号 #,是时候要想个办法偷懒了。可考考虑在 ~/.zshrc (或 ~/.bash_profile) 添加自定义函数进行切换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
gnu_utils=(
  PACKAGE
  # the other utils ...
)

gnu() {
  for _util in "${gnu_utils[@]}"; do
    export PATH="/usr/local/opt/$_util/libexec/gnubin:$PATH"
  done
  [[ $1 == "--quiet" ]] || echo "Switched to GNU utils!"
}

bsd() {
  for _util in "${gnu_utils[@]}"; do
    export PATH="$(echo $PATH | sed "s|/usr/local/opt/$_util/libexec/gnubin:||")"
  done
  echo "Switched to BSD utils!"
}

gnu --quiet

添加完成后运行 source ~/.zshrc 或重启终端使函数生效。上述命令很简单:gnu() 把 GNU 工具的执行路径插入环境变量 PATH,而 bsd() 负责从 PATH 里面清除 GNU 的执行路径。因为需要在终端启动时默认使用 GNU,在上段代码结尾调用了 gnu

以后切换命令工具的版本时,在终端直接指定期望的工具名称即可:

  • 切换到 GNU 工具:

    1
    2
    
    $ gnu
    Switched to GNU utils!
    
  • 切换到 BSD 工具:

    1
    2
    
    $ bsd
    Switched to BSD utils!
    

现在,Mac 可以当作是换皮 Linux 来写脚本了,性价比再加一分。

参考资料

This post is licensed under CC BY 4.0 by the author.

Comments powered by Disqus.