---
title: "ให้แจ้งเตือนเมื่อคำสั่งยาว ๆ ในเทอร์มินัลทำงานเสร็จ"
description: "เลิกนั่งเฝ้าบิลด์ที่ใช้เวลาสี่สิบนาที แค่ต่อหนึ่งบรรทัดหลังคำสั่งใด ๆ ผลลัพธ์ก็ส่งถึงมือถือ แม้คุณจะเดินออกไปแล้วก็ตาม"
date: 2026-09-09
author: Nooc
authorAvatarLink: /images/avatars/nooc.webp
authorLink: https://nooc.me
tags:
  - เทอร์มินัล
  - เชลล์
  - แจ้งเตือนบิลด์
  - AI ช่วยเขียนโค้ด
  - การแจ้งเตือนผ่าน webhook
---

# ให้แจ้งเตือนเมื่อคำสั่งยาว ๆ ในเทอร์มินัลทำงานเสร็จ

คุณสั่งเทรนโมเดล รันชุดทดสอบทั้งชุด สั่ง `docker build` หรือปล่อยให้ AI ช่วยเขียนโค้ดทำงานยาว ๆ สักอย่าง จากนั้นก็เหลือทางเลือกแย่ ๆ สองทาง คือนั่งจ้องแถบความคืบหน้า หรือเดินออกไปแล้วกลับมาอีกยี่สิบนาทีให้หลังเพื่อพบว่ามันพังไปตั้งแต่วินาทีที่เก้าสิบ

มีทางที่สาม และมันยาวแค่บรรทัดเดียว

## บรรทัดนั้น

สร้างแชนเนลใน Echobell คัดลอก URL ของ webhook แล้วต่อท้ายสิ่งที่คุณกำลังรัน

```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=$?` ต้องอยู่ถัดจากคำสั่งทันที อะไรก็ตามที่แทรกกลาง รวมถึงตัวทดสอบ `[` เอง จะเขียนทับ `$?`

## ทำให้เป็นฟังก์ชันของเชลล์

พิมพ์แบบนั้นทุกครั้งก็เสียจุดประสงค์ ใส่อันนี้ลงใน `~/.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
}
```

ตั้งค่า `ECHOBELL_HOOK` ในโปรไฟล์เชลล์ หรือดีกว่านั้นคืออย่าเอาไว้ใน repo dotfiles เลย ให้ export จากไฟล์ที่คุณไม่ commit แล้วก็ใช้แบบนี้

```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"
```

ชี้ `notify` ไปที่แชนเนลที่สองสำหรับงานที่คุณยอมลุกจากเตียง ส่วนการรันที่สำเร็จก็เงียบ ๆ ผ่านไป

## อยู่รอดจากการปิดฝาโน้ตบุ๊กและ SSH ที่หลุด

ถ้าคำสั่งรันผ่าน SSH การปิดฝาโน้ตบุ๊กจะฆ่าเชลล์ทิ้ง และการแจ้งเตือนก็ไม่มีวันถูกส่ง แก้ได้สองทาง

**tmux** — เริ่มคำสั่งในเซสชันแล้ว detach ออกมา

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

**nohup** — สำหรับงานครั้งเดียว

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

ทั้งสองวิธีทำให้โปรเซสอยู่รอดนานกว่าการเชื่อมต่อของคุณ และการแจ้งเตือนก็มาถึงไม่ว่าคุณจะยังต่ออยู่หรือไม่

## AI ช่วยเขียนโค้ด

รูปแบบเดียวกันนี้ใช้กับ AI แบบคอมมานด์ไลน์ที่กำลังทำงานยาว ๆ ได้เลย

```bash
notify codex exec "รีแฟกเตอร์โมดูลชำระเงินแล้วรันเทสต์"
```

ส่วน AI ที่หยุดกลางทางเพื่อรอคน เช่น รออนุมัติหรือรอให้กดยืนยันสิทธิ์ การแจ้งเตือนแบบ "ทำเสร็จแล้ว" เป็นเครื่องมือที่ผิด เพราะมันยังไม่เสร็จ กรณีนั้นต้องใช้ hook หรือ callback ของตัว AI เอง และควรเป็นสายโทรมากกว่าการแจ้งเตือนธรรมดา [เปลี่ยนจุดรออนุมัติของ AI ให้เป็นสายโทรเข้า](/th/blog/ai-agent-human-in-the-loop-alerts) พูดถึงเรื่องนี้โดยตรง รวมถึง `Notification` hook ของ Claude Code และ matcher ชื่อ `agent_needs_input`

ใช้ทั้งสองอย่าง hook สำหรับ "ติดอยู่" และฟังก์ชันเชลล์สำหรับ "ทำเสร็จแล้ว"

## สิ่งที่ไม่ควรใส่ในการแจ้งเตือน

**อย่าใส่เอาต์พุตของคำสั่ง** มันน่าดึงดูดที่จะเอาบรรทัดท้าย ๆ ของบิลด์ที่พังใส่ลงในเนื้อหา แต่อดใจไว้ ล็อกบิลด์มีโทเคน สตริงการเชื่อมต่อ และข้อมูลลูกค้าบ่อยกว่าที่คนส่วนใหญ่คิด และเนื้อหาการแจ้งเตือนก็ไปโผล่บนหน้าจอล็อก ส่งแค่รหัสสถานะพอ แล้วค่อยกลับไปดูเทอร์มินัลเอง

**อย่าเอา URL ของแชนเนลไปไว้ใน repo 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 ว่างแล้ว

## สรุป

ฟังก์ชันเชลล์หนึ่งอัน แชนเนลหนึ่งอัน และอีกหนึ่งเงื่อนไขถ้าคุณอยากรับเฉพาะตอนล้มเหลว ตั้งค่าราวสองนาที แลกกับยี่สิบนาทีที่เคยเสียไปกับการนั่งจ้องแถบความคืบหน้า

[ดาวน์โหลด Echobell สำหรับ iPhone](https://apps.apple.com/app/apple-store/id6743597198?pt=128151925&ct=blog-notify-when-terminal-command-finishes-th&mt=8) หรือ [รับบน Google Play](https://play.google.com/store/apps/details?id=one.echobell.echobellandroid) แล้วลองกับงานที่นานพอจะทำให้คุณเดินออกไปได้

---

## เนื้อหาที่เกี่ยวข้อง

- [เปลี่ยนจุดรออนุมัติของ AI ให้เป็นสายโทรเข้า](/th/blog/ai-agent-human-in-the-loop-alerts)
- [อย่าพลาดความล้มเหลวของ GitHub Actions](/th/blog/github-actions-notifications)
- [แจ้งเตือนเมื่อ cron job ล้มเหลว](/th/blog/cron-job-failure-alerts)
- [เอกสารทริกเกอร์ webhook](/th/docs/webhook)
- [คู่มืออ้างอิงเงื่อนไขแชนเนล](/th/docs/conditions)
