Use Gum in Nushell Script for Better UIUX
Charm 团队推出了一系列 CLI/TUI 工具,其中就包括 OpenCode 的前身crush: Glamourous agentic coding for all 💘
本文要介绍的 Gum 是 Charmbracelet 推出的另一个 CLI 工具,让你在 Shell 脚本中轻松添加交互式组件——选择菜单、文本输入、确认弹窗、加载动画等,不用手写 TUI 逻辑。
Install
brew install gumscoop install charm-gum# Archpacman -S gumCommon GOTCHAs
在 Bash/Zsh 等 UNIX-Compatible Shell 里用 gum 很简单,官方仓库也提供了示例,本文不再赘述。
Nushell 定义了一套不同的语法,在使用 gum 时与 Bash 有些许不同。
Text 📝
Nushell 中外部命令的输出可以直接赋值给变量,基本不会踩坑:let choice = ^gum choose "feat" "fix" "docs" "chore" --header "Choose a language"print $choice
当外部命令与 Nushell built-in command/alias重名时可以用 ^ 来明确调用外部程序。
加不加都行,但建议加上
关于多选
需要注意的就是多选。输出结果是多行文本,gum 的多行文本并不会被自动转成 list,需要自己处理类型# 多选(--no-limit 或 --limit N)let langs = (gum choose --no-limit "Rust" "Go" "TypeScript" "Python")let lang_list = ($langs | lines)
同样,Nushell 的 list 不能直接 pipe 给 gum,需要先转成多行文本:# ✅ 正确:先 join 成换行分隔的文本let items = ["apple", "banana", "cherry"]let pick = ($items | str join "\n" | gum filter)
不然就会得到如下输出:$ let pick = ($items | gum filter)> Filter...• ╭───┬────────╮ │ 0 │ apple │ │ 1 │ banana │ │ 2 │ cherry │ ╰───┴────────╯
Exit Code ☑️
在 Shell 中,命令的退出码 0 表示成功,非 0 表示失败;gum 的 confirm 子命令便是利用退出码反映用户的选择结果。
基于退出码,Bash 可以实现两种典型逻辑:
- 当 Command 1 成功 → 执行 Command 2
- 当 Command 1 失败 → 执行 Command 2
下面是对应的写法
# 先执行 command1# ↓# 若退出码为 0(成功)# ↓# 则继续执行 command2command1 && command2# 等价的 if 写法command1if [ $? -eq 0 ]; then command2fi# 先执行 command1# ↓# 若退出码 ≠ 0(失败)# ↓# 则继续执行 command2command1 || command2# 等价的 if 写法command1if [ $? -ne 0 ]; then command2fi而在 Nushell 中,&& / || 这种 Bash 的「基于退出码的短路执行」并不存在。但 Nushell 同样提供了退出码,你可以用普通的 if 判断命令是否成功。
最直接的写法是读取 $env.LAST_EXIT_CODE:gum confirm;if $env.LAST_EXIT_CODE == 0 { print "Deleting..."}
你也可以使用 () 操作符,Nushell 会依次执行 () 内的语句并返回最终的结果
因此可以把 gum confirm 与 $env.LAST_EXIT_CODE == 0 包裹成一个 block,让代码变得更紧凑if (^gum confirm; $env.LAST_EXIT_CODE == 0) { print "Deleting..."}
也可为其封装一个函数:def confirm [msg: string] { ^gum confirm $msg $env.LAST_EXIT_CODE == 0}if confirm "Delete file?" { print "Deleting..."}
Cheatsheet
终端输出美化
gum style --foreground 212 --border-foreground 212 --border double --padding "1 2" --margin "1" "Hello from Nushell"╔══════════════════════╗║ ║║ Hello from Nushell ║║ ║╚══════════════════════╝常用参数:
--foreground/--background: 文字/背景颜色(数字或十六进制)--border/--border-foreground: 边框样式(single,double,rounded等)和颜色--padding/--margin: 内边距/外边距(格式:"上下 左右"或"上 左右 下")--align: 文本对齐(left,center,right)
# 带时间戳的结构化日志gum log --time rfc822 --structured --level debug "Creating file..." name file.txt# 10 Mar 26 22:55 CST DEBUG Creating file... name=file.txt# 不同日志级别gum log --level error "Something went wrong"gum log --level info "Deployment complete"--time 支持的格式:rfc822, unix, date, date-time, datetime 等。
# Markdown 渲染gum format -- "# Gum Formats" "- Markdown" "- Code" "- Template" "- Emoji"Gum Formats• Markdown• Code• Template• Emoji# 语法高亮cat main.go | gum format -t code# Emoji 解析echo 'I :heart: Bubble Gum :candy:' | gum format -t emoji# 水平拼接gum join "A" "B" "C"# 垂直拼接gum join --vertical "A" "B" "C"# 配合 style 构建复杂布局let I = (gum style --padding "1 5" --border double --border-foreground 212 "I")let LOVE = (gum style --padding "1 4" --border double --border-foreground 57 "LOVE")gum join --vertical $I $LOVESpin
# 基本用法gum spin --spinner dot --title "正在安装依赖..." -- bun install# 显示命令输出gum spin --spinner dot --title "正在安装依赖..." --show-output -- bun installbun install v1.3.10 (30e609e0)Checked 274 installs across 321 packages (no changes) [14.00ms]可用 spinner:line, dot, minidot, jump, pulse, points, globe, moon, monkey, meter, hamburger
Gum 美化
gum 的各子命令支持通过环境变量设置默认样式,避免每次调用都写一堆参数。喜欢 Catppuccin 的可以参考下面的步骤:
- 执行
wget https://raw.githubusercontent.com/holo96/catppuccin-gum/refs/heads/main/gum-catppuccin.sh -O ~/.config/gum/theme.sh - 在脚本中
source theme.sh
#!/usr/bin/env bash[ -f ~/.config/gum/theme.sh ] && source ~/.config/gum/theme.sh# ...写在最后
一开始写这篇post的时候,感觉不如直接用 Bash 得了
但相信NuShell确实是更好的选择,跨平台matters
Use Gum in Nushell Script for Better UIUX