最近看《Unix 编程艺术》的时候看到一段,
也许 Unix 最持久的异议恰恰来自 Unix 哲学的一个特性,这一条特性是 X window 设计者首先明确提出的。X 致力于提供一套“机制,而不是策略”,以支持一套极端通用的图形操作,从而把使用工具箱和界面的“观感”(策略)推后到应用层。 Unix 其它系统级的服务也有类似的倾向:行为的最终逻辑被尽可能推后到使用端。Unix用户可以在多种 shell 中进行选择。而 Unix 应用程序通常会提供很多的行为选项和令人眼花缭乱的定制功能。
于是我想要尝试一些 zsh 以外的 shell。让豆包推荐了几个 shell 以后,选择了跨平台的 Nushell,作为老 Windows 用户不得不支持一下。
豆包推荐的几个里很有特色的:
- Nushell (Nu) — 最现代、数据型 Shell
- 主打:像写代码一样用 Shell,结构化数据
- 输出不是纯文本,是表格 / JSON / 对象
- 天生支持:ls 彩色、语法高亮、自动补全
- 跨平台:Linux/macOS/Windows 完全一致
- 语法干净、现代,比 Bash 强太多
- 适合:开发者、喜欢清爽结构化命令的人
- Xonsh — Python 风格的 Shell
- 主打:Python + Shell 混合写
- 直接在命令行写 Python 代码
- 兼容 Bash 命令
- 可高度可编程
- 适合:Python 开发者
但是 Nushell 也有缺点:
- Nushell 不兼容 POSIX 标准,导致不支持
exportsourceeval等语法,无法直接使用source ~/.profile完全无痛迁移 shell 通用的配置,一些工具链配置 shell 环境的时候也会有问题。Nushell 文档里提供了一个函数来导入其它脚本的变量,可以参考官方文档 (opens new window)。

# 安装 Nushell
- Linux:从包管理器安装
nushell,如果官方源没有的话,可以从 brew 安装brew install nushell starship。 - Windows:
choco install -y nushell starship,或者从 GitHub (opens new window) 下载。安装以后重启终端,Nushell 就出现在终端 App 的选项里了。
# Nushell 启动
nu
# Linux 设置 Nushell 为默认 shell
sudo $nu.current-exe -c '$nu.current-exe | save -a /etc/shells'
chsh -s $nu.current-exe
# Linux Nushell 导入其它 Shell 的环境变量
nu 的一个常见问题是,其他应用程序将环境变量或功能导出为 shell 脚本,这些脚本期望由你的 shell 运行。 但许多应用程序只考虑最常用的 shell,如 bash 或 zsh。不幸的是,nu 与这些 shell 的语法完全不兼容,因此无法直接运行或 source 这些脚本。 通常,通过调用 zsh 本身(如果已安装)来运行 zsh 脚本没有任何障碍。但不幸的是,这将不允许 nu 访问导出的环境变量:
# 这可以工作,使用 zsh 打印 "Hello"
'echo Hello' | zsh -c $in
# 这会退出并报错,因为 $env.VAR 未定义
'export VAR="Hello"' | zsh -c $in
print $env.VAR
文档提供了一个函数来解决这个问题。它的原理是启动一个 shell 来运行脚本,捕获运行前后的环境变量,然后比较两者的差异来确定哪些变量被更改了,最后把这些更改的变量导入到 nushell 的环境中。
# Linux 更新 Nushell 配置文件
我的 Linux Nushell 配置文件做了几件事:
- 通过文档提供的函数
capture-foreign-env,捕获~/.profile的内容,追加到了$nu.config-path里,这样每次启动 Nushell 的时候就会自动导入~/.profile里的环境变量了。 - 因为
PATH变量在~/.profile里是一个字符串,而 Nushell 期望它是一个列表,所以这里做了特殊处理,把它转换成列表。 - 手动设置了
LANG和LC_ALL变量,解决了 Nushell 的 locale 错误问题。
Windows 没有遇到这些问题,所以就不需要更新配置文件了,保持默认配置。
下面的命令行直接把这些配置写入了 $nu.config-path。
'def capture-foreign-env [
--shell (-s): string = /bin/sh
# 运行脚本的 shell
#(必须支持 '-c' 参数和 POSIX 'env'、'echo'、'eval' 命令)
--arguments (-a): list<string> = []
# 传递给外部 shell 的额外命令行参数
] {
let script_contents = $in;
let env_out = with-env { SCRIPT_TO_SOURCE: $script_contents } {
^$shell ...$arguments -c `
env
echo '<ENV_CAPTURE_EVAL_FENCE>'
eval "$SCRIPT_TO_SOURCE"
echo '<ENV_CAPTURE_EVAL_FENCE>'
env -0 -u _ -u _AST_FEATURES -u SHLVL` # 过滤掉已知的更改变量
}
| split row '<ENV_CAPTURE_EVAL_FENCE>'
| {
before: ($in | first | str trim | lines)
after: ($in | last | str trim | split row (char --integer 0))
}
# 不幸的假设:
# 没有更改的环境变量包含换行符(无法干净解析)
$env_out.after
| where { |line| $line not-in $env_out.before } # 只获取更改的行
| parse "{key}={value}"
| transpose --header-row --as-record
| if $in == [] { {} } else { $in }
}
if ('~/.profile' | path exists) {load-env (open ~/.profile | capture-foreign-env --shell bash)}
if (($env.PATH | describe) == 'string') { $env.PATH = $env.PATH | split row (char esep) } # PATH 变量需要特殊处理,因为它是一个字符串,而 nushell 期望它是一个列表
$env.LANG = "zh_CN.UTF-8"
$env.LC_ALL = "zh_CN.UTF-8"
' | save -f $nu.config-path
# Nushell 配置图标 (Starship)
- Windows 安装:如果上面没有用 choco 安装的话,还可以用 winget 安装:
winget install --id Starship.Starship。Windows 安装完以后可能需要重新启动终端。 - Linux 安装:从包管理器安装 starship,或者
curl -sS https://starship.rs/install.sh | sh
安装好以后,执行下面的命令来配置 Nushell 的 Starship:
mkdir ($nu.data-dir | path join "vendor/autoload")
starship init nu | save -f ($nu.data-dir | path join "vendor/autoload/starship.nu")
'eval "$(starship init bash)"' | save -f ~/.bashrc # 顺便配置一下 bash 的 starship
nu # 重启 Nushell
我个人喜欢在 Git Status 里显示数量,所以在 ~/.config/starship.toml 里添加了下面的配置:
'[git_status]
disabled = false
format = '([\[$all_status$ahead_behind\]]($style) )'
style = "red bold"
stashed = '\$${count}'
ahead = "⇡${count}"
behind = "⇣${count}"
diverged = "⇕${count}"
conflicted = "=${count}"
deleted = "✘${count}"
renamed = "»${count}"
modified = "!${count}"
staged = "+${count}"
untracked = "?${count}"
' | save -f ~/.config/starship.toml