---
title: "终端里的长命令跑完了，让它通知你"
description: "别再守着一个跑四十分钟的构建。在任意命令后面加一行，结果就会送到手机上——包括你早就走开的那种情况。"
date: 2026-09-09
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - 终端
  - Shell
  - 构建通知
  - AI 编程助手
  - Webhook 通知
---

# 终端里的长命令跑完了，让它通知你

你启动了一次训练、一整套测试、一个 `docker build`，或者一个 AI 编程助手去处理一项耗时的任务。然后你只有两个糟糕的选项：坐着盯进度条，或者走开二十分钟回来发现它在第九十秒就失败了。

其实还有第三个选项，一行就够。

## 那一行

在 Echobell 里建一个频道，复制它的 webhook URL，然后接在你要跑的命令后面：

```bash
pnpm build; curl -sS -X POST https://hook.echobell.one/t/YOUR_TOKEN \
  -H 'content-type: application/json' \
  -d '{"title":"构建结束","body":"echobell-web"}'
```

注意这里是 `;` 而不是 `&&`。用 `&&` 的话，只有命令成功时才会发通知——而这恰好是反的：失败才是你最想知道的那种情况。

## 把退出码也带上

一条只说「结束了」的通知只讲了一半。把状态一起捕获：

```bash
pnpm build; s=$?; curl -sS -X POST https://hook.echobell.one/t/YOUR_TOKEN \
  -H 'content-type: application/json' \
  -d "{\"title\":\"构建 $([ $s -eq 0 ] && echo 成功 || echo 失败)\",\"status\":\"$s\"}"
```

`s=$?` 必须紧跟在命令后面。中间夹任何东西——包括 `[` 这个测试命令本身——都会把 `$?` 覆盖掉。

## 做成一个 shell 函数

每次都手打这一串就失去意义了。把这个放进 `~/.zshrc` 或 `~/.bashrc`：

```bash
notify() {
  "$@"
  local status=$?
  curl -sS -X POST "$ECHOBELL_HOOK" \
    -H 'content-type: application/json' \
    -d "{\"command\":\"$*\",\"status\":\"$status\",\"host\":\"$(hostname -s)\"}" \
    >/dev/null
  return $status
}
```

在 shell 配置里设置 `ECHOBELL_HOOK`——更好的做法是别把它放进会提交的 dotfiles 仓库，而是从一个不纳入版本控制的文件里 export。然后：

```bash
notify pnpm test
notify cargo build --release
notify python train.py
```

结尾那句 `return $status` 很重要：它让 `notify` 保持透明，所以 `notify make && ./deploy.sh` 依然按你预期的方式工作。

配合上面的 payload，频道模板可以写成：

**标题**

```
{{command}} · {{status}}
```

**正文**

```
在 {{host}} 上
```

## 只有失败时才叫我

大多数运行都会成功，你并不需要听到它们。用两个频道能干净地解决：一个普通频道收全部，一个来电频道加条件：

```
status != "0"
```

把值得你从床上爬起来的那些任务指向第二个频道，成功的运行就安静地过去了。

## 让它扛得住合上笔记本和断掉的 SSH

如果命令跑在 SSH 上，合上笔记本会杀掉这个 shell，通知也就永远不会发出去。两种解法：

**tmux** —— 在会话里启动然后 detach：

```bash
tmux new -d -s build 'notify pnpm build'
```

**nohup** —— 临时跑一次：

```bash
nohup bash -c 'notify pnpm build' >/dev/null 2>&1 &
```

两种方式都能让进程活过你的连接，通知会照常送达，不管你还连着没有。

## AI 编程助手

同样的模式也适用于一个在处理长任务的 CLI 助手：

```bash
notify codex exec "重构支付模块并跑一遍测试"
```

至于那些跑到一半停下来等人的助手——比如需要审批、需要授权确认——完成通知是错的工具，因为它根本还没完成。那种情况需要的是助手自己的 hook 或回调，而且值得一通电话而不是一条推送。[把 AI 助手的审批卡点变成一通电话](/zh/blog/ai-agent-human-in-the-loop-alerts)专门讲了这件事，包括 Claude Code 的 `Notification` hook 和它的 `agent_needs_input` matcher。

两个都用：hook 负责「我卡住了」，shell 函数负责「我干完了」。

## 别往通知里放什么

**别放命令输出。** 把失败构建的最后几行塞进正文很有诱惑力，但请忍住——构建日志里出现 token、连接串和客户数据的频率比大多数人以为的高，而通知正文最终会显示在锁屏上。发退出码就够了，然后自己回终端看。

**别把频道 URL 放进公开的 dotfiles 仓库。** 拿到这个 URL 的人都能往你的频道发消息。把它放在不纳入版本控制的文件里，并打开 **仅 POST**，这样一个随手的链接预览也没法触发它。

## 常见问题

### 为什么不用 `terminal-notifier` 或 `notify-send`？

那些是在跑命令的那台机器上弹通知。你人在跟前时它们没问题。这套方案针对的正是你不在跟前的情况——远程机器、合上的笔记本、另一个房间。

### Windows 上能用吗？

模式一样，语法不同。PowerShell 里等价写法是 `Invoke-RestMethod -Method Post -Uri $env:ECHOBELL_HOOK -ContentType application/json -Body $json`，用 `$LASTEXITCODE` 代替 `$?`。

### 能推到手表上吗？

可以。订阅会送达配对的 Apple Watch，而「构建好了」这件事，手表其实是最合适的形态。

### 如果命令要跑 12 个小时呢？

这套方案里没有任何超时——命令什么时候返回，`curl` 就什么时候执行。用 tmux 跑，免得连接断了把进程一起带走。

### 队友能收到同样的通知吗？

可以。把频道分享出去，每个订阅者自己选通知类型。共用一台训练机的时候很有用，不止一个人关心 GPU 什么时候空出来。

## 小结

一个 shell 函数、一个频道，只想要失败通知的话再加一个条件。设置大概花两分钟，换回你盯进度条的那二十分钟。

[下载 iPhone 版 Echobell](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-notify-when-terminal-command-finishes-zh&mt=8) 或[在 Google Play 获取](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid)，然后找一个长到值得你走开的任务试一次。

---

## 相关内容

- [把 AI 助手的审批卡点变成一通电话](/zh/blog/ai-agent-human-in-the-loop-alerts)
- [不再错过 GitHub Actions 失败](/zh/blog/github-actions-notifications)
- [定时任务失败告警](/zh/blog/cron-job-failure-alerts)
- [Webhook 触发文档](/zh/docs/webhook)
- [频道条件参考](/zh/docs/conditions)
