由于个人对 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
如何做配置同步共享呢?
思路非常简单:
- 将配置文件存放于 iCloud 云盘,可多设备同步。
- 在本地配置中引入 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
对应如下:
其中 LocalHostName
是 ComputerName
格式化之后的名称,比如空格会替换为连字符 -
,一些特殊符号也会被忽略。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.