最近看《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 目前在高延迟 SSH 场景下,输入延迟相比 POSIX shell 高很多(nushell#14474 (opens new window)),于是我在服务器上又换回了 zsh,再观望一段时间。
- Nushell 不兼容 POSIX 标准,导致不支持
exportsourceeval等语法,无法直接使用source ~/.profile完全无痛迁移 shell 通用的配置,一些工具链配置 shell 环境的时候也会有问题。有开发者实现了一个 Nushell 用于导入其它脚本(如~/.profile)的变量,可以参考GitHub (opens new window)。 - 基于 bash 写的工具没法用(例如
nvm),需要用的时候还是要切回bash或zsh来执行。 - 基于 bash 写的命令补全工具(例如
dockersystemctl)也没法用,不过可以用 Carapace 来补全这些工具的命令。 - 目前还没有成熟的依赖/插件/模块管理器,nushell/nupm (opens new window) 还在开发中,现在一切都依靠手动下载,然后在配置文件里
use和source来加载。没有一键脚本,这样配环境还是很麻烦。

# 安装 Nushell
- Linux:
- 从包管理器安装
nushell。 - 如果官方源没有的话,可以从 brew 安装:
- 从包管理器安装
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install nushell starship carapace
- Windows:
- 使用 Chocolatey 安装:
choco install -y nushell starship
- 或者从 GitHub (opens new window) 下载。
- 安装以后重启终端,Nushell 就出现在终端 App 的选项里了。
# Nushell 启动
nu
# Linux 设置 Nushell 为默认 shell
sudo $nu.current-exe -c '$nu.current-exe + "\n" | save -a /etc/shells'
chsh -s $nu.current-exe
# Nushell 配置图标 (Starship)
- Windows 安装:如果上面没有用 choco 安装的话,还可以用 winget 安装:
winget install --id Starship.Starship。Windows 安装完以后可能需要重新启动终端。 - Linux 安装:从包管理器安装
starship,或者从 brew 安装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
# 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 的环境中。
不过在 GitHub tesujimath/bash-env-nushell (opens new window) 上有另一个实现,下面的配置将会使用这个实现。
# Nushell 安装命令补全工具 (Carapace)
- Windows 安装:
winget install -e --id rsteube.Carapace - Linux 安装:从包管理器安装
carapace,或者从 brew 安装carapace,又或者从 releases (opens new window) 下载后配置到 PATH 里。
然后在 env.nv 和 config.nu 里添加下面的配置来启用 carapace(之所以要放到两个文件,是因为 Nushell 更像是编译型语言,不支持动态 source 刚创建的脚本):
## $nu.env-path
$env.CARAPACE_BRIDGES = 'zsh,fish,bash,inshellisense'
mkdir $"($nu.cache-dir)"
carapace _carapace nushell | save --force $"($nu.cache-dir)/carapace.nu"
# $nu.config-path
source $"($nu.cache-dir)/carapace.nu"
# Linux 更新 Nushell 配置文件
我的 Linux Nushell 配置文件做了几件事:
- 导入 Carapace 的命令补全功能,这样就可以在 Nushell 里使用 Carapace 来补全各种工具的命令了;
- 导入我自己编写的
module-install.nu模块,这个模块提供了module-install函数,用于一键下载模块,并在配置文件中自动导入它; - 导入
bash-env模块,捕获~/.profile里的环境变量,这样每次启动 Nushell 的时候就会自动导入~/.profile里的环境变量了。 - 因为
PATH变量在~/.profile里是一个字符串,而 Nushell 期望它是一个列表,所以这里做了特殊处理,把它转换成列表。 - 手动设置了
LANG和LC_ALL变量,解决了 Nushell 的 locale 错误问题。
Windows 没有遇到这些问题,所以就不需要更新配置文件了,保持默认配置。
由于涉及到多个文件的配置,下面的命令由上往下执行。
下载 module-install.nu 函数以及 bash-env.nu 模块(以及依赖的 bash-env-json 脚本):
cd $nu.default-config-dir
mkdir scripts modules ~/.local/bin
http get https://public.lyh543.cn/setup-server/nushell/module-install.nu | save -f scripts/module-install.nu
http get https://raw.githubusercontent.com/tesujimath/bash-env-nushell/refs/heads/main/bash-env.nu | save -f modules/bash-env.nu
http get https://raw.githubusercontent.com/tesujimath/bash-env-json/refs/heads/main/bash-env-json | save -f ~/.local/bin/bash-env-json
chmod a+x ~/.local/bin/bash-env-json
更新 config.nu 来加载 bash-env.nu 模块,并从 ~/.profile 导入环境变量:
'# 加载 base-env.nu 来从 .profile 中提取环境变量
$env.PATH ++= [($env.HOME | path join ".local/bin")]
$env.NUSHELL_IMPORT_BASH_ENV = 1 # 在 ~/.profile 里可以用这个环境变量来判断是否在 Nushell 里,从而跳过一些不必要的环境变量设置
use modules/bash-env.nu
if ('~/.profile' | path exists) {bash-env ~/.profile | load-env}
hide-env NUSHELL_IMPORT_BASH_ENV # 导入完以后就删除这个环境变量
# PATH 变量需要特殊处理,因为它是一个字符串,而 nushell 期望它是一个列表
if (($env.PATH | describe) == 'string') { $env.PATH = $env.PATH | split row (char esep) }
$env.LANG = "zh_CN.UTF-8"
$env.LC_ALL = "zh_CN.UTF-8"
source $"($nu.cache-dir)/carapace.nu"
source scripts/module-install.nu
' | save -f $nu.config-path
'$env.CARAPACE_BRIDGES = 'zsh,fish,bash,inshellisense'
mkdir $"($nu.cache-dir)"
carapace _carapace nushell | save --force $"($nu.cache-dir)/carapace.nu"
' | save -f $nu.env-path
然后重新启动 nu,就可以看到 PATH 被正确地导入了:
__ ,
.--()°'.' Welcome to Nushell,
'|, . ,' based on the nu language,
!_-(_\ where all data is structured!
Version: 0.110.0 (x86_64-unknown-linux-gnu)
Please join our Discord community at https://discord.gg/NtAbbGn
Our GitHub repository is at https://github.com/nushell/nushell
Our Documentation is located at https://nushell.sh
And the Latest Nushell News at https://nushell.sh/blog/
Learn how to remove this at: https://nushell.sh/book/configuration.html#remove-welcome-message
It's been this long since Nushell's first commit:
6yrs 9months 8days 22hrs 28mins 30secs 150ms 302µs 349ns
Startup Time: 361ms 878µs 756ns
liu in 🌐 Home-Server in ~
❯ echo $env.PATH
╭────┬──────────────────────────────────────────────────╮
│ 0 │ /home/liu/.cargo/bin │
│ 1 │ /home/liu/.local/lib/go/bin │
│ 2 │ /home/liu/.fvm_flutter/bin │
│ 3 │ /home/liu/.pyenv/shims │
│ 4 │ /home/liu/.pyenv/bin │
│ 5 │ /home/liu/.nvm/versions/node/v22.12.0/bin │
│ 6 │ /usr/lib/jvm/default/bin │
│ 7 │ /usr/local/sbin │
│ 8 │ /usr/local/bin │
│ 9 │ /usr/bin │
│ 10 │ /home/liu/.local/bin │
│ 11 │ /home/liu/.local/bin │
│ 12 │ /home/liu/git/github/dev-tools/shell │
│ 13 │ /home/liu/.pnpm-global/bin │
│ 14 │ /home/liu/.yarn/bin │
│ 15 │ /home/liu/.pub-cache/bin │
│ 16 │ /home/liu/.local/share/JetBrains/Toolbox/scripts │
│ 17 │ /home/liu/.local/lib/Android/Sdk/platform-tools │
╰────┴──────────────────────────────────────────────────╯