Mac 系统终端美化与 ZSH 多设备配置同步共享

配图源自 Freepik

由于个人对 iTerm2 等第三方终端工具不太感冒,因此一直在用系统内置终端。相比那些花里胡哨的东西,系统自带的「Terminal」可谓是简陋啊,哈哈~

今天,终于有空折腾一下它了。

此前的样子。

一、为什么要使用 zsh?

在 Unix-like 操作系统中供选择的 Shell 解析器种类有很多,最常见的是 bash 和 zsh。

相较之下,zsh 主要有以下优点:

  • 完全兼容 bash,因此 !# /bin/bash 这类脚本也是完美支持的。
  • 更强大的 Tab 补全。
  • 更智能的目录切换。
  • 命令选项补全。
  • 文件、目录大小写自动更正(最最最直观的感受,太方便了)。
  • 丰富的主题、插件(Oh My Zsh)
  • 可查看命令输入历史记录。

总的来说,Tab 大法好,有事没事 Tab 一下,可以帮你省掉很多命令的输入。

自 macOS 10.15 起,系统使用 zsh 作为默认 Shell。如果你还在用 bash,可参考「在 Mac 上将 zsh 用作默认 Shell」一文进行迁移。

二、安装 Oh My Zsh

zsh 的功能很多,但是配置较为繁琐,详细配置请看 Archlinux Zsh。

但是,Robby Russell 开发的 Oh My Zsh 项目极大地改变了配置繁琐的现象,而且带来了强大的插件和主题功能。

安装

$ sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

安装完成后,它被存放于 ~/.oh-my-zsh 目录,在里面我们可以找到它提供的任何插件、主题等。

需要注意的是,此前的 ~/.zshrc 配置文件会被重命名为 ~/.zshrc.pre-oh-my-zsh。若有需要,可将此前的配置重新添加到 ~/.zshrc 里面。

启用插件

Oh My Zsh 提供的插件很多很多很多,详见 Plugins Wiki。如需启用某插件,只需在 ~/.zshrc 引入即可。多个插件之间使用「空白符」隔开,但不能用逗号 , 隔开,否则会被中断。

举个例子,以下将会按顺序启用 nvm、git、ruby 插件。

plugins=(nvm git ruby)

切换主题

Oh My Zsh 提供了超过 150 种主题,默认主题是 robbyrussell,详见 Themes Wiki。同样地,如需配置主题,则添加到 ~/.zshrc 里面。

ZSH_THEME="robbyrussell"

它还提供了一些花里胡哨的配置,比如随机主题。

# 每打开一个新的 Shell 窗口,随机选择内置的主题。
ZSH_THEME="random"

# 或者从多个候选主题中随机展示。
ZSH_THEME_RANDOM_CANDIDATES=(
  "robbyrussell"
  "agnoster"
)

# 甚至可以排除掉不喜欢的主题,这样就不会在随机主题中出现。
ZSH_THEME_RANDOM_IGNORED=(pygmalion tjkirch_mod)

我大致看了下内置的主题,似乎没有能够满足我的强迫症的,因此后面会自定义。

三、主题 DIY

一番折腾之后的效果:

设置窗体、字体等颜色

系统内置的终端工具默认主题「Basic」是白底黑字的。虽然也内置了几种主题供选择,但架不住它丑啊。由于个人不太喜欢深色模式,因此 Basic 这个主题用着还不错。

于是,就自定义了一个新主题「Dark Themer」,并提交到 GitHub toFrankie/terminal-theme。

其实命令输出中花里胡哨的颜色,都是读取 ANSI 颜色的配置而已,如果你想要自定义,自行修改即可。

也可以新增、删除、导入或导出主题,点击下方左侧对应图标即可。

自定义命令提示符

我这里使用了 Oh My Zsh 的默认主题 robbyrussell,其命令提示符为:

PROMPT="%(?:%{$fg_bold[green]%}➜ :%{$fg_bold[red]%}➜ )"
PROMPT+=' %{$fg[cyan]%}%c%{$reset_color%} $(git_prompt_info)'

个人不太喜欢,于是改成了这样:

PROMPT="%(?:%{$fg_bold[green]%}→ ●:%{$fg_bold[red]%}→ ●)%{$fg_bold[white]%}●"
PROMPT+=" %{$fg_bold[yellow]%}%c %{$reset_color%}$ "
RPROMPT="[%{$fg[white]%}%*%{$reset_color%}]"

前后对比效果如下:

配置 vim

如果 vim 模式下仍然是「白底」,可以在 ~/.vimrc 中添加以下配置:

set background=dark "设置背景色

这样的话,主题就统一了。

四、配置同步

由于我这边有 MacBook Pro 和 iMac 两个设备,那么设备间同步就显得很重要了,可以省去修改其一设备配置,需要拷贝到另一设备以同步更新的麻烦。

首先,我们的 zsh 配置都是用户级别的,也就是存放在 ~ 目录下:

~/.zshrc

如何做配置同步共享呢?

思路非常简单:

  1. 将配置文件存放于 iCloud 云盘,可多设备同步。
  2. 在本地配置中引入 iCloud 云盘同步的配置文件。

首先,我们在 iCloud 云盘创建一个目录,比如 Terminal

$ cd ~/Library/Mobile\ Documents/com~apple~CloudDocs
$ mkdir Terminal

然后,将 ~/.zshrc 拷贝至 Terminal 目录,并重命名为 zshrc.sh

$ cp ~/.zshrc ~/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc.sh

以上创建目录、拷贝、重命名等操作可直接在 Finder 中完成。

接着,修改本地的 ~/.zshrc,可以移除掉里面原先的配置,然后写入一行配置即可:source $HOME/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc.sh,可直接使用以下命令完成:

$ echo 'source $HOME/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc.sh' > ~/.zshrc

所以 ~/.zshrc 配置文件就变成了:

source $HOME/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc.sh

source 命令用于执行一个脚本,通常用于重新加载一个配置文件。

最后,刷新下 ~/.zshrc 配置文件。

$ source ~/.zshrc

在其他设备下,只要修改一次本地配置文件,并刷新配置即可。

配置拆分

前面做配置同步的初衷是解决多设备的场景,另一方面是可以做备份,下次换机器可省去配置的麻烦。

既然是多设备,那极有可能各设备的环境是不一样的,也就是配置会有所不同。基于此场景,直接采用上述方式显然是不妥的。假设设备 A 安装了 nvm,然后在 iCloud 云盘 Terminal/zshrc.sh 里添加了对应的配置,而设备 B 是没有安装 nvm 的,但由于 B 共享了配置,在 B 设备中执行 souce ~/.zshrc 就会导致出错。

因此,我们要对 Terminal/zshrc.sh 配置进行拆分:

~/Library/Mobile Documents/com~apple~CloudDocs/Terminal/
├── zshrc.sh            # 统一引入以下细分配置
├── zshrc-common.sh     # 各设备共同配置,比如 Oh-My-Zsh 的一些配置等
├── zshrc-imac.sh       # iMac 设备特有配置
└── zshrc-mbp.sh        # MacBook Pro 设备特有配置 

以上按自个实际情况拆分。这里文件名不以 . 开头的原因是不想隐藏文件,毕竟它不存在于本地用户的主目录下,没必要隐藏了。另外,扩展名是可省略的,甚至可随意自定义。由于 ~/.zshrc 本身就是一个 Shell 脚本,而 Shell 脚本扩展名是可选的。但按习惯,多将其定义为 .sh,加之主流编辑器对此扩展名一般都有语法高亮。

按设备引入对应配置

首先,我们现在 Terminal/zshrc.sh 引入 zshrc-common.sh 共同的配置文件。

# zshrc.sh

# 加载共同配置
source $HOME/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc-common.sh

# 加载设备特有配置
# 而且应放在共同配置后面加载,原则上它们的优先级应该较高的。
# ...

接着,要解决按设备引入各自特有的配置文件。用以下几种方法来区分设备:

$ hostname
Frankies-iMac.local

$ scutil --get LocalHostName
Frankies-iMac

$ scutil --get ComputerName
Frankie's iMac

对应如下:

其中 LocalHostNameComputerName 格式化之后的名称,比如空格会替换为连字符 -,一些特殊符号也会被忽略。hostname 则是在 LocalHostName 加上 .local 的名称。但由于 hostname 读取顺序的问题(详见),它可能会受到所连接网络的影响,因此它可能不是固定值。

基于以上考虑,这里使用 scutil --get ComputerName 命令,它的返回值取决于你输入的电脑名称。但注意 ComputerName 为 macOS 独有。

接着,编写一个 Shell 函数,按设备电脑名称加载对应配置。

# 根据设备电脑名称,读取对应配置
function load_device_zsh_configuration() {
  local computer_name=$(scutil --get ComputerName)
  local icloud_config_dir="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Terminal"
  if [[ $computer_name =~ 'MacBook' ]]; then
    local config_file="$icloud_config_dir/zshrc-mbp.sh"
    if [ -f "$config_file" ]; then
      source "$config_file"
    fi
  elif [[ $computer_name =~ 'iMac' ]]; then
    local config_file="$icloud_config_dir/zshrc-imac.sh"
    if [ -f "$config_file" ]; then
      source "$config_file"
    fi
  fi
}
load_device_zsh_configuration
unset -f load_device_zsh_configuration

以上几个地方按需调整。一是 icloud_config_dir 后面的目录,也就是 iCloud 云盘中存放配置文件的目录。二是类似 $computer_name =~ 'MacBook' 这行,意思是当 ComputerName 中包含 MacBook 字符串时,执行 if 语句,将加载 zshrc-mbp.sh 配置。

因此,zshrc.sh 的配置如下:

# Load zsh common configuration
source $HOME/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc-common.sh

# Load device zsh configuration
function load_device_zsh_configuration() {
  local computer_name=$(scutil --get ComputerName)
  local icloud_config_dir="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Terminal"
  if [[ $computer_name =~ 'MacBook' ]]; then
    local config_file="$icloud_config_dir/zshrc-mbp.sh"
    if [ -f "$config_file" ]; then
      source "$config_file"
    fi
  elif [[ $computer_name =~ 'iMac' ]]; then
    local config_file="$icloud_config_dir/zshrc-imac.sh"
    if [ -f "$config_file" ]; then
      source "$config_file"
    fi
  fi
}
load_device_zsh_configuration
unset -f load_device_zsh_configuration

一次失败的尝试与思考

最初,我有一种利用链接文件特性的思路:

Terminal/zshrc.sh~/.zshrc 建立硬链接关系,此时两者就是真正意义上的同一文件(inode 号码相同),修改其中一方,另一方会同步修改。而且删除任意一方,对另一方不会产生影响。

但是,由于 iCloud 云盘中的文件会进行同步操作。据我观察,当文件同步完成之后,尽管文件名不会发生改变,但 inode 号码会发生变化的。这样的话,会导致前面建立的硬链接关系破裂,即 Terminal/zshrc.sh~/.zshrc 的 inode 不再相同,修改任意一方,另一方都不会同步修改。因此,基于硬链接的思路是不行的。

然后,我又萌生了另一种思路:利用软链接文件。软链接文件的文件内容存放的是文件路径,前面 iCloud 同步不会影响文件名,因此看起来似乎是可行的:

# 创建 Terminal/zshrc.sh 的软链接至 ~/.zshrc.icloud
$ ln -s ~/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc.sh ~/.zshrc.icloud

# 在 ~/.zshrc 写入配置
$ echo 'source ~/.zshrc.icloud' > ~/.zshrc

# 刷新配置
$ souce ~/.zshrc

以上在 ~/.zshrc 加载 ~/.zshrc.icloud 的时候,由于后者是 Terminal/zshrc.sh 的软链接,命令执行时会找到 Terminal/zshrc.sh 的内容进行加载。

这种方案看着是可行的,但属实有点多次一举了。何不直接在 ~/.zshrc 加载 Terminal/zshrc.sh 呢?

若对硬链接与软链接文件特效不太了解的话,可看 Linux 链接文件详解。

其他

完成以上多设备同步、配置共享之后,后续配置调整,应根据实际情况对以下文件作出修改,而不是直接修改 ~/.zshrc 本地配置。

~/Library/Mobile Documents/com~apple~CloudDocs/Terminal/
├── zshrc-common.sh
├── zshrc-imac.sh
└── zshrc-mbp.sh

由于 iCloud 云盘的 Terminal 目录不好记,还长,加之容易忽略 Mobile\ Documents 空格的问题。因此,我们可以在 zshrc-common.sh 设置一些变量或别名以快速定位到配置文件。以下仅供参考:

# zshrc-common.sh

# 刷新本地配置
alias sourcez="source ~/.zshrc"

# 设置别名,可快速打开各配置文件
alias openz-common="open $HOME/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc-common.sh"
alias openz-imac="open $HOME/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc-imac.sh"
alias openz-mbp="open $HOME/Library/Mobile\ Documents/com~apple~CloudDocs/Terminal/zshrc-mbp.sh"

# 设置变量,可直接通过 `cd $ICLOUD_TERMINAL_DIR` 命令打开目录
export ICLOUD_TERMINAL_DIR="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Terminal"

这样的话,比如要往 zshrc-imac.sh 中添加配置,可以这样:

# 打开 zshrc-imac.sh 配置文件
$ openz-imac

# 往 zshrc-imac.sh 配置文件中添加一行配置:export CUR_DEVICE="Frankie's iMac"

$ 刷新配置
# soucez

# 测试一下
$ echo $CUR_DEVICE
Frankie's iMac

甚至,可以给每个配置文件设置一个变量 Configuration_File_Name,然后利用类似 echo 'some configuration' >> $Configuration_File_Name 形式往配置文件中追加配置。请自行发挥。

The end.

你可能感兴趣的:(Mac 系统终端美化与 ZSH 多设备配置同步共享)