oh-my-zsh bgnotify 插件深度指南:为长时间运行的命令添加跨平台后台完成通知

发布时间:2026/9/18 1:59:15
oh-my-zsh bgnotify 插件深度指南:为长时间运行的命令添加跨平台后台完成通知
oh-my-zsh bgnotify 插件深度指南为长时间运行的命令添加跨平台后台完成通知【免费下载链接】ohmyzsh A delightful community-driven (with 2,500 contributors) framework for managing your zsh configuration. Includes 300 optional plugins (rails, git, macOS, hub, docker, homebrew, node, php, python, etc), 140 themes to spice up your morning, and an auto-update tool that makes it easy to keep up with the latest updates from the community.项目地址: https://gitcode.com/gh_mirrors/oh/ohmyzsh导读bgnotify 是 Oh My Zsh 内置的一款跨平台后台通知插件当终端中长时间运行的命令结束时它会通过 macOS、Linux 或 Windows 上的系统通知机制弹出提醒让你可以放心切走去做别的事。本文基于 plugins/bgnotify/README.md 与插件源码 plugins/bgnotify/bgnotify.plugin.zsh 展开读完你将掌握它的安装依赖、触发原理、四个核心配置项以及如何通过自定义bgnotify_formatted函数打造完全属于自己的通知样式。插件概览它解决什么问题在终端里跑编译、打包、测试、部署等耗时命令时你往往会切到浏览器或编辑器里做别的事情等命令跑完再回来。如果命令很快结束还好一旦耗时几十秒甚至几分钟你就得频繁回看终端效率极低。bgnotify 的思路非常直接记录每条命令开始执行的时间点在命令结束后的提示符刷新阶段precmd 钩子检查耗时是否超过阈值如果超过且此时终端窗口不是当前前台应用就调用系统通知工具弹出桌面通知内容包含命令的退出状态和实际耗时。它支持 macOS、Linux 与 WindowsCygwin且实现完全基于 zsh 内置的钩子机制不依赖任何外部守护进程安装成本极低。该插件最初源自独立的开源项目 zsh-background-notify后被 Oh My Zsh 收录为内置插件。环境依赖各平台需要准备的通知工具插件本身只是调度器真正弹出通知的是各个平台上的通知命令。使用时请先确认当前系统具备以下任一工具平台通知工具安装方式macOSterminal-notifierbrew install terminal-notifier或gem install terminal-notifiermacOS可选growlnotifyGrowl 配套命令行工具优先级低于terminal-notifierLinuxGNOME 系notify-sendUbuntu 等发行版通常已预装libnotify-bin 提供LinuxKDE 系kdialogKDE 桌面环境自带WindowsCygwinnotifu官方二进制安装后加入 PATH或使用 Cygwin Ports 的 libnotify 包插件在运行时会按固定优先级探测这些命令是否存在详见下文通知后端分发因此无需任何配置即可自动适配平台。需要特别指出的是当这些工具都不存在时插件不会弹出任何通知也不会报错所以在使用前先用command -v notify-send或对应命令确认环境。安装启用加入 plugins 列表即可启用方式和 Oh My Zsh 其他插件完全一致在.zshrc的plugins(...)数组中追加bgnotify例如plugins(git bgnotify) source $ZSH/oh-my-zsh.shOh My Zsh 启动时会遍历plugins数组依次 source 每个插件的name.plugin.zsh文件——这个加载循环在 oh-my-zsh.sh 中实现且插件目录会先被加入fpath见 oh-my-zsh.sh。因此启用 bgnotify 后无需额外执行任何初始化命令重新打开终端或执行source ~/.zshrc即可生效。.zshrc模板中的插件配置示例见 templates/zshrc.zsh-template。配置顺序的关键约束下面介绍的配置变量如bgnotify_threshold必须在source $ZSH/oh-my-zsh.sh之前定义因为插件的默认值采用${var:-default}形式只有在变量尚未定义时才会写入默认值。若你在 source 之后再设置默认值已生效你的设置会被覆盖。工作原理preexec / precmd 钩子驱动的通知流水线要深入理解 bgnotify需要先认识它依赖的两个 zsh 钩子preexec每条命令执行前触发和precmd每次显示新提示符前触发。插件在 bgnotify.plugin.zsh 中注册了这两个钩子autoload -Uz add-zsh-hook add-zsh-hook preexec bgnotify_begin add-zsh-hook precmd bgnotify_end加载保护非交互会话与 SSH 直接跳过插件开头有两道保护bgnotify.plugin.zsh[[ -o interactive ]] || return # dont load on non-interactive shells [[ -z $SSH_CLIENT -z $SSH_TTY ]] || return # dont load on a SSH connection即非交互式 shell如脚本执行的子 shell和 SSH 远程会话中插件直接不加载。前者避免污染脚本输出后者则是因为远程终端通常没有本地桌面通知可用。计时与阈值判定bgnotify_begin / bgnotify_end插件通过zmodload zsh/datetime加载 zsh 内置时间模块使用$EPOCHSECONDSUnix 时间戳计时源码注释明确说明这比调用外部date命令更快bgnotify.plugin.zsh。bgnotify_beginL13-L16在命令执行前记录开始时间与命令文本function bgnotify_begin { bgnotify_timestamp$EPOCHSECONDS bgnotify_lastcmd${1:-$2} }bgnotify_endL18-L34在命令结束后依次做四步判定全部通过才发送通知function bgnotify_end { { local exit_status$? local elapsed$(( EPOCHSECONDS - bgnotify_timestamp )) # check time elapsed [[ $bgnotify_timestamp -gt 0 ]] || return 0 [[ $elapsed -ge $bgnotify_threshold ]] || return 0 # check if Terminal app is not active [[ $(bgnotify_appid) ! $bgnotify_termid ]] || return 0 bgnotify_formatted $exit_status $bgnotify_lastcmd $elapsed } always { bgnotify_timestamp0 } }判定逻辑拆解如下退出状态$?捕获命令的退出码供自定义通知文案区分成功/失败耗时下限elapsed bgnotify_threshold才通知避免命令瞬时完成时弹出无意义提醒前台应用检测调用bgnotify_appid获取当前前台应用标识若与启动时缓存的终端标识bgnotify_termid相同说明用户正盯着终端直接跳过通知发送调用bgnotify_formatted用户可自定义。always块保证无论是否触发通知计时器都会被重置为 0避免下一次判定使用过期时间戳。前台应用检测多平台窗口焦点识别bgnotify_appidL62-L78按平台分支探测当前聚焦的应用这是后台通知体验的关键——只有终端不在前台时才打扰你macOS优先用lsappinfo查询前台应用的 bundle id无lsappinfo时退化为osascript调用 AppleScriptWayland sway/i3检测到$WAYLAND_DISPLAY与$SWAYSOCK/$I3SOCK且存在swaymsg时调用bgnotify_find_sway_appid解析聚焦窗口Niri 合成器存在$NIRI_SOCKET时调用bgnotify_find_niri_appid通过niri msg -j windows查询X11通过xprop -root _NET_ACTIVE_WINDOW获取活动窗口兜底以上均不可用时输出当前时间戳一个永远与缓存值不相同的值效果是始终通知。其中 Wayland 下的bgnotify_find_sway_appidL81-L110实现颇为讲究优先使用jq解析swaymsg -t get_tree的 JSON若系统没有jq则回退到一段纯awk解析通过跟踪括号嵌套层级找到focused: true所在块并提取其app_id与id。另外插件在加载末尾执行bgnotify_termid$(bgnotify_appid)L153——因为 macOS 上bgnotify_appid调用较慢且终端标识基本不变所以启动时缓存一次即可。配置项详解bgnotify 提供四个配置入口均通过环境变量或函数覆盖实现配置项类型默认值作用bgnotify_bell布尔值true是否在通知时同时触发终端响铃printf \abgnotify_threshold整数秒见下方说明命令运行超过该秒数才弹通知bgnotify_formatted函数内置实现完全自定义通知标题、正文与图标bgnotify_extraargs字符串空追加到通知命令末尾的额外参数bgnotify_bell终端响铃开关默认值为true即命令完成时终端会响一声铃即使桌面通知因故未能弹出也能提供听觉反馈。设置为false可关闭bgnotify_bellfalse默认实现的源码见 bgnotify.plugin.zsh[[ $bgnotify_bell true ]] printf \a。bgnotify_threshold通知阈值秒命令执行时长达到该值含才会通知。这里存在一个文档与源码的差异需要注意README 中写默认值为 6 秒而当前仓库源码 bgnotify.plugin.zsh 的默认值为5 秒bgnotify_threshold${bgnotify_threshold:-5}注释亦为 5s。实际行为以源码为准——未配置时阈值为 5 秒。你也可以显式覆盖例如设为 4 秒bgnotify_threshold4bgnotify_extraargs透传额外参数追加到最终通知命令尾部的额外参数典型用途是让notify-send生成瞬时通知-e参数Linux 上通知自动消失而不驻留通知栏bgnotify_extraargs-e注意该参数是按字面透传的不同通知工具的参数语法不同需要自行确认兼容性。内置默认通知格式若不自定义bgnotify_formatted插件使用内置实现bgnotify.plugin.zshfunction bgnotify_formatted { local exit_status$1 local cmd$2 # humanly readable elapsed time local elapsed$(( $3 % 60 ))s (( $3 60 )) || elapsed$((( $3 % 3600) / 60 ))m $elapsed (( $3 3600 )) || elapsed$(( $3 / 3600 ))h $elapsed [[ $bgnotify_bell true ]] printf \a # beep sound if [[ $exit_status -eq 0 ]]; then bgnotify #win (took $elapsed) $cmd else bgnotify #fail (took $elapsed) $cmd fi }它会把秒数耗时格式化为人类可读的Xh Ym Zs形式按bgnotify_bell决定是否响铃成功时通知标题为#win、失败为#fail。最终调用bgnotify 标题 命令文本完成发送。自定义通知bgnotify_formatted 完全指南bgnotify_formatted接收三个位置参数$1为退出状态码$2为执行的命令文本$3为耗时秒数。你可以在source $ZSH/oh-my-zsh.sh之前定义同名函数覆盖默认实现。README 给出了完整示例这里结合源码注释完整呈现含变量名补全与注释bgnotify_bellfalse ## disable terminal bell bgnotify_threshold4 ## set your own notification threshold function bgnotify_formatted { ## $1exit_status, $2command, $3elapsed_time # Humanly readable elapsed time local elapsed$(( $3 % 60 ))s (( $3 60 )) || elapsed$((( $3 % 3600) / 60 ))m $elapsed (( $3 3600 )) || elapsed$(( $3 / 3600 ))h $elapsed [ $1 -eq 0 ] titleHoly Smokes Batman || titleHoly Graf Zeppelin [ $1 -eq 0 ] icon$HOME/icons/success.png || icon$HOME/icons/fail.png bgnotify $title - took ${elapsed} $2 $icon } plugins(git bgnotify) ## add to plugins list source $ZSH/oh-my-zsh.sh ## existing source call这个示例展示了自定义的三个典型能力自定义文案将默认的#win/#fail替换为任何文本比如加入耗时信息自定义图标bgnotify的第三个参数是图标路径会被映射为对应工具的图标参数macOS 的-appIcon、Linux 的--icon、Windows 的/i示例中按成败分别使用不同的 PNG自定义逻辑耗时格式化、条件判断都完全由你掌控。一个更实用的增强版示例同时展示bgnotify_extraargs的用法bgnotify_threshold10 bgnotify_extraargs-e # notify-send 瞬时通知 function bgnotify_formatted { local exit_status$1 cmd$2 elapsed$3 local elapsed_fmt$(( elapsed % 60 ))s (( elapsed 60 )) || elapsed_fmt$(((elapsed % 3600) / 60))m $elapsed_fmt (( elapsed 3600 )) || elapsed_fmt$((elapsed / 3600))h $elapsed_fmt local title icon if [[ $exit_status -eq 0 ]]; then title✅ 命令完成耗时 $elapsed_fmt icon$HOME/.icons/success.png else title❌ 命令失败耗时 $elapsed_fmt icon$HOME/.icons/fail.png fi bgnotify $title $cmd $icon }由于插件通过(( ${functions[bgnotify_formatted]} ))判断函数是否已存在bgnotify.plugin.zsh只要你在 source 前定义过内置实现就不会被加载。通知后端分发bgnotify 的跨平台调度所有自定义bgnotify_formatted最终都调用bgnotify函数发送通知。该函数bgnotify.plugin.zsh按命令存在性依次分发function bgnotify { local title$1 local message$2 local icon$3 if (( ${commands[terminal-notifier]} )); then # macOS local term_id$(bgnotify_programid) terminal-notifier -message $message -title $title ${icon:-appIcon $icon} ${term_id:-activate $term_id} ${bgnotify_extraargs:-} /dev/null elif (( ${commands[growlnotify]} )); then # macOS growl growlnotify -m $title $message ${bgnotify_extraargs:-} elif (( ${commands[notify-send]} )); then notify-send $title $message ${icon:--icon $icon} ${bgnotify_extraargs:-} elif (( ${commands[kdialog]} )); then # KDE kdialog --title $title --passivepopup $message 5 ${bgnotify_extraargs:-} elif (( ${commands[notifu]} )); then # cygwin notifu /m $message /p $title ${icon:/i $icon} ${bgnotify_extraargs:-} fi }各后端的参数差异要点terminal-notifiermacOS 首选除标题和正文外还通过-activate指定点击通知后激活的终端应用。bgnotify_programidL118-L124根据$TERM_PROGRAM映射 bundle idiTerm 2 为com.googlecode.iterm2、Apple Terminal 为com.apple.terminal、Ghostty 为com.mitchellh.ghosttygrowlnotifymacOS 备选仅在terminal-notifier缺失时使用优先级更低notify-sendLinux 首选图标参数为--iconkdialogKDE使用 5 秒的--passivepopup被动弹窗notifuCygwin图标参数为/i。优先级设计保证了只装一个工具即可工作且多平台配置完全复用无需为不同系统维护多份配置。常见问题与调试建议配置不生效确认所有bgnotify_*变量与bgnotify_formatted函数定义在source $ZSH/oh-my-zsh.sh之前插件默认值采用${var:-default}惰性赋值source 之后再设置会被覆盖。命令跑完没有通知按顺序排查——① 确认notify-send/terminal-notifier/kdialog等工具存在② 确认命令耗时达到bgnotify_threshold③ 确认命令执行期间终端处于前台——bgnotify 的设计就是终端在前台不打扰④ 若在 SSH 会话或非交互 shell 中插件根本不会加载。README 与源码阈值不一致README 声称默认 6 秒当前仓库源码实际默认为 5 秒。需要精确阈值时请显式设置bgnotify_threshold不要依赖默认值。只想关闭通知不需要从plugins中删除插件设置bgnotify_threshold0之外的任意超大值如bgnotify_threshold86400即可让通知几乎永不触发同时保留响铃等其他能力。Wayland 下的兼容性sway 场景需要swaymsg通常随 sway 自带jq非必需但推荐安装——没有jq时插件会回退到 awk 解析功能等价但更慢。排查加载是否成功在交互式终端执行echo $plugins确认列表包含bgnotify并执行whence -w bgnotify确认函数已定义若未定义多半是 shell 不满足交互/非 SSH 前提。总的来说bgnotify 是 Oh My Zsh 中小但完整的典范约 150 行源码通过两条 zsh 钩子、一次前台应用检测和一组命令探测分发就实现了跨三个桌面生态的可靠后台通知。理解它的 hook 流程后你还可以借同样的思路preexec 计时 precmd 判定扩展出自己的终端自动化能力。【免费下载链接】ohmyzsh A delightful community-driven (with 2,500 contributors) framework for managing your zsh configuration. Includes 300 optional plugins (rails, git, macOS, hub, docker, homebrew, node, php, python, etc), 140 themes to spice up your morning, and an auto-update tool that makes it easy to keep up with the latest updates from the community.项目地址: https://gitcode.com/gh_mirrors/oh/ohmyzsh创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考