watch 的核心原理
watch 的本质很简单:循环执行命令 + 全屏显示输出。但它的实现细节值得深挖。
底层实现机制
// watch 的简化实现逻辑
int main(int argc, char **argv) {
while (1) {
clear_screen(); // 清屏
print_header(); // 显示标题栏
system(argv[1]); // 执行命令
sleep(interval); // 等待间隔
if (exit_on_change) break; // 检测变化退出
}
}
实际的 watch 实现使用 execvp() 执行命令(而非 system()),通过 ncurses 库 控制 终端显示,并精确控制信号处理。
关键参数详解
-n 间隔控制
默认 2 秒刷新,但可以自定义:
# 每秒刷新(高频监控)
watch -n 1 nvidia-smi
# 每 10 秒健康检查
watch -n 10 "curl -s http://localhost/health"
最小间隔是 0.1 秒(watch -n 0.1),但要注意频繁执行命令可能影响性能。
-d 变化高亮
这是 watch 的杀手锏功能。它会对比前后两次输出,高亮显示变化的部分:
# 高亮内存变化
watch -d free -m
# 高亮 GPU 显存分配
watch -d nvidia-smi
实现原理:将输出按字符分割,对比每个字符位置的变化。高亮使用 ANSI 转义序列(\e[7m 反色显示)。
-g 变化退出
这个参数让 watch 从监控器变成事件触发器:
# 文件变化时退出(用于脚本等待)
watch -g "ls -l output.txt"
echo "文件已变化!"
# 等待进程出现
watch -g "pgrep -f 'python train.py'"
echo "训练进程已启动"
实现逻辑:将当前输出存入缓冲区,与上一次输出对比,不同则退出循环。
实战场景深度剖析
场景一:GPU 训练监控
# 监控 GPU 使用率和显存
watch -n 1 -d nvidia-smi
输出会实时高亮显存变化、利用率波动,非常适合 深度学习 训练监控。
场景二:端口监听追踪
# 监控 TCP 端口变化
watch -n 1 -d "ss -tlnp | grep 8080"
当服务启动时,端口状态从 LISTEN 变为可见,-d 会高亮这一变化。
场景三:文件传输进度
# 监控大文件复制进度
watch -d "ls -lh backup.tar.gz"
文件大小变化会实时高亮,比反复执行 ls 直观得多。
场景四:自动化脚本触发
#!/bin/bash
# 等待日志文件生成
watch -g "ls /var/log/app.log 2>/dev/null"
# 文件出现后执行后续操作
echo "日志文件已生成,开始处理..."
tail -f /var/log/app.log
性能考量与陷阱
命令管道的陷阱
# 错误:管道需要引号
watch ps aux | grep nginx # 只会监控 ps aux
# 正确:整体命令需要引号
watch "ps aux | grep nginx"
原因:watch 只接受一个命令参数,管道在 shell 解析时被拆分。
高频监控的性能影响
# 每秒执行 10 次(过度)
watch -n 0.1 "find / -name '*.log'"
频繁执行复杂命令会占用大量 CPU 和 I/O 资源。建议:
- 简单命令(如
free)可以 1 秒刷新 - 复杂命令(如
find)至少 5 秒间隔 - 网络请求建议 10 秒以上
ANSI 颜色处理
# 默认不解析颜色代码
watch "ls --color=auto" # 颜色代码显示为乱码
# 使用 -c 参数解析颜色
watch -c "ls --color=auto" # 正确显示颜色
Web 实现:浏览器版 watch
前端实现 watch 功能的核心思路:
// 浏览器版 watch 实现
async function watchCommand(command: string, interval: number, onHighlight: (diff: string[]) => void) {
let lastOutput = '';
while (true) {
const output = await executeCommand(command);
const diff = highlightDiff(lastOutput, output);
onHighlight(diff);
lastOutput = output;
await sleep(interval);
}
}
// 高亮差异实现
function highlightDiff(oldText: string, newText: string): string[] {
const oldLines = oldText.split('\n');
const newLines = newText.split('\n');
const result: string[] = [];
newLines.forEach((line, i) => {
if (oldLines[i] !== line) {
result.push(`[变更] ${line}`); // 高亮标记
} else {
result.push(line);
}
});
return result;
}
浏览器无法直接执行系统命令,需要通过 WebSocket 连接后端代理,或使用 Web Terminal 方案(xterm.js)。
相关命令对比
| 命令 | 用途 | 是否实时 | 变化检测 |
|---|---|---|---|
watch | 定期执行显示 | 是 | 支持(-d) |
top/htop | 进程监控 | 是 | 自动刷新 |
tail -f | 日志追踪 | 是 | 无 |
tmux | 终端复用 | - | 手动切换 |
相关工具
- Linux top 命令 - 实时进程监控
- Linux htop 命令 - 交互式进程监控器
- Linux tail 命令 - 实时日志追踪