1. ANSI 转义序列基础
一句话总结: 终端通过 ANSI 转义序列(
\033[开头)控制颜色、光标与屏幕,脚本据此画出动态界面。
终端本质是「接收字节流的渲染器」,特定字节序列不是字符而是指令。
# 最基础:\033[31m 红色,\033[0m 重置
echo -e "\033[31m红色文字\033[0m 正常文字"
# 用 printf 更清晰
printf '\033[32m绿色\033[0m\n'
printf '\033[1;34m粗体蓝色\033[0m\n'
1.1 颜色表速查
前景色 3037,背景色 4047,加粗 1,下划线 4,重置 0。
# 前景色
for c in 30 31 32 33 34 35 36 37; do
printf '\033[%sm%s\033[0m ' "$c" "$c"
done
echo
# 背景色
for c in 40 41 42 43 44 45 46 47; do
printf '\033[%sm%s\033[0m ' "$c" "$c"
done
echo
# 组合样式
printf '\033[1;31;43m加粗红字黄底\033[0m\n'
一句话总结: 记忆规律是「3x 前景 / 4x 背景 / 1 加粗」,结尾务必
\033[0m重置,否则颜色泄漏到后续所有输出。
1.2 256 色与真彩色
# 256 色: \033[38;5;N m
printf '\033[38;5;196m256色红\033[0m\n'
# 真彩色: \033[38;2;R;G;B m
printf '\033[38;2;255;128;0m橙橙色\033[0m\n'
# 检测终端颜色支持
echo "$TERM"
[ "$TERM" = "xterm-256color" ] && echo "支持 256 色"
2. 光标控制与单行刷新
一句话总结: 光标移动序列让脚本能在同一位置反复绘制,
\r回车是「回到行首重写」实现进度条的最小前提。
# \r 回车 + 不换行:重写当前行
printf '进度 10%%\r'
sleep 1
printf '进度 50%%\r'
sleep 1
printf '进度 100%%\n'
# 光标上移一行: \033[1A
printf '第一行\n'
printf '\033[1A重写第一行\033[0K\n'
# 清行: \033[2K 清除整行, \033[0K 清除到行尾
printf '残留内容\033[2K新内容\n'
2.1 隐藏光标与恢复
# 隐藏光标: \033[?25l, 恢复: \033[?25h
printf '\033[?25l隐藏光标\n'
sleep 1
printf '\033[?25h恢复光标\n'
一句话总结:
\r重写单行、\033[1A上移多行、\033[2K清行、\033[?25l藏光标——四个序列拼出一切动态界面。
2.2 tput 可移植写法
tput 根据 terminfo 生成正确序列,比硬编码转义更可移植。
# tput 等价写法
tput setaf 2; echo "绿色"; tput sgr0
tput cup 3 10; echo "第3行第10列"
# 常用 tput 命令
bold=$(tput bold)
red=$(tput setaf 1)
reset=$(tput sgr0)
echo "${bold}${red}告警${reset}"
3. 进度条实现
一句话总结: 进度条 = 循环里用
\r重写一行,按完成比例绘制「百分比 + 填充块」,配合真实任务计算进度。
#!/usr/bin/env bash
# 最简进度条
total=100
for ((i=0; i<=total; i+=10)); do
printf '进度: %3d%%\r' "$i"
sleep 0.2
done
echo "进度: 100%"
3.1 带填充块的进度条
#!/usr/bin/env bash
progress() {
local total="$1" cur="$2"
local width=40
local filled=$((cur * width / total))
local bar=""
for ((i=0; i<width; i++)); do
[ "$i" -lt "$filled" ] && bar+="#" || bar+="-"
done
printf '[%s] %3d%%\r' "$bar" "$((cur * 100 / total))"
}
for ((i=0; i<=20; i++)); do
progress 20 "$i"
sleep 0.1
done
echo ""
一句话总结: 填充块数量 =
进度 / 总数 * 宽度,用整数运算避免浮点,\r让进度条停留在同一行。
3.2 带真实任务的进度
#!/usr/bin/env bash
set -euo pipefail
# 模拟下载:按文件数推进进度
files=(a.zip b.zip c.zip d.zip e.zip)
total=${#files[@]}
cur=0
for f in "${files[@]}"; do
# 真实任务
sleep 0.5
((cur+=1))
printf '下载 %s... [%-20s] %3d%%\r' \
"$f" "$(printf '#%.0s' $(seq 1 $((cur * 20 / total))))" \
$((cur * 100 / total))
done
echo ""
4. 交互式输入与菜单
一句话总结:
read -p带提示、read -s隐藏输入、read -n单键读取,三个组合实现交互式菜单与密码输入。
#!/usr/bin/env bash
# 带提示的读取
read -r -p "请输入名字: " name
echo "你好, $name"
# 隐藏输入(密码)
read -r -s -p "密码: " pass
echo ""
echo "密码长度: ${#pass}"
4.1 单键菜单选择
#!/usr/bin/env bash
echo "请选择操作:"
echo " [1] 构建"
echo " [2] 测试"
echo " [3] 部署"
read -r -n 1 -p "> " choice
echo ""
case "$choice" in
1) echo "执行构建" ;;
2) echo "执行测试" ;;
3) echo "执行部署" ;;
*) echo "无效选择: $choice" ;;
esac
一句话总结:
read -n 1读单键不用回车、read -s隐藏回显、read -r防反斜杠转义,三件套覆盖交互输入。
4.2 带默认值的输入
#!/usr/bin/env bash
read -r -p "端口 [8080]: " port
port="${port:-8080}"
echo "使用端口: $port"
# 超时控制
if read -r -t 5 -p "5秒内确认 (y/N): " ans; then
[[ "$ans" =~ ^[Yy]$ ]] && echo "已确认" || echo "已取消"
else
echo "超时, 默认取消"
fi
5. 信号捕获与终端状态恢复
一句话总结: 动态界面脚本被 Ctrl+C 中断时可能留下隐藏光标、关闭的回显,必须用 trap 恢复终端状态。
#!/usr/bin/env bash
# 记录原始状态并在退出时恢复
save_state() {
stty_save=$(stty -g)
trap 'tput cnorm; stty "$stty_save"; exit 1' INT TERM EXIT
stty -echo # 关闭回显
tput civis # 隐藏光标
# ... 动态界面逻辑 ...
tput cnorm # 恢复光标
stty "$stty_save"
}
save_state
5.1 trap 清理的标准写法
#!/usr/bin/env bash
set -euo pipefail
cleanup() {
tput cnorm # 恢复光标
stty echo # 恢复回显
echo "" # 换行避免残留
}
trap cleanup EXIT INT TERM
# 隐藏光标并进入原始模式
tput civis
stty -echo
# 动态任务
for i in $(seq 1 20); do
printf '工作中 %2d/20\r' "$i"
sleep 0.1
done
# 退出时自动 cleanup
一句话总结:
trap cleanup EXIT INT TERM让「退出即恢复终端」,stty -echo与tput civis成对出现、成对恢复。
6. 多行动态输出
一句话总结: 用
\033[NA上移 N 行再重写,可以维护「多行状态区」,比如同时展示多个任务的进度。
#!/usr/bin/env bash
# 多行进度区: 每次重写后光标回到顶部
tasks=(任务A 任务B 任务C)
lines=${#tasks[@]}
for round in $(seq 1 5); do
for t in "${tasks[@]}"; do
printf '%s: 第 %d 轮\r' "$t" "$round"
printf '\033[1B' # 下移一行
done
printf '\033[%dA' "$lines" # 回到顶部
sleep 0.3
done
echo ""
6.1 tput 维护整屏
#!/usr/bin/env bash
# 清屏并重绘
tput clear
tput cup 1 2; echo "标题"
tput cup 3 2; echo "状态: 运行中"
tput cup 5 2; echo "按 q 退出"
# 读取屏幕尺寸
echo "行数: $(tput lines), 列数: $(tput cols)"
一句话总结:
tput clear清屏、tput cup 行 列定位,适合画「整屏仪表盘」,配合循环定时重绘。
6.2 彩色状态徽章
#!/usr/bin/env bash
ok="\033[32m✔\033[0m"
fail="\033[31m✘\033[0m"
printf "检查配置 %b\n" "$ok"
printf "检查依赖 %b\n" "$ok"
printf "检查权限 %b\n" "$fail"
7. 实战:带 UI 的备份脚本
一句话总结: 把颜色、进度条、交互确认、信号清理组合成「生产级外观」的脚本,让终端自动化既好看又可中断。
7.1 完整示例
#!/usr/bin/env bash
set -euo pipefail
# 颜色与工具
c_green=$(tput setaf 2)
c_red=$(tput setaf 1)
c_yellow=$(tput setaf 3)
c_reset=$(tput sgr0)
cleanup() { tput cnorm; echo ""; }
trap cleanup EXIT INT TERM
log() { printf '%s[%s]%s %s\n' "$c_green" "$(date +%T)" "$c_reset" "$*"; }
warn() { printf '%s[警告]%s %s\n' "$c_yellow" "$c_reset" "$*"; }
die() { printf '%s[错误]%s %s\n' "$c_red" "$c_reset" "$*" >&2; exit 1; }
tput civis
log "开始备份 /data → /backup"
for f in /data/*; do
cp -r "$f" /backup/ || warn "跳过 $f"
printf '已处理: %s\r' "$(basename "$f")"
done
echo ""
log "备份完成"
一句话总结: 抽象
log/warn/die三个着色函数 +trap cleanup,动态脚本的「外观」与「安全」同时到位。
7.2 进度 + 交互确认
#!/usr/bin/env bash
set -euo pipefail
trap 'tput cnorm; exit 1' INT TERM
read -r -p "确认清理 3 天前的日志? [y/N] " ans
[[ "$ans" =~ ^[Yy]$ ]] || exit 0
find /var/log -type f -mtime +3 -print0 \
| xargs -0 -r rm -f
echo "清理完成"
7.3 跨平台终端差异
#!/usr/bin/env bash
# 非终端环境(管道/CI)禁用颜色
if [ ! -t 1 ]; then
c_green=""; c_red=""; c_yellow=""; c_reset=""
fi
echo "${c_green}${c_reset}输出到文件时无颜色代码"
# 检测终端类型
case "$TERM" in
*256color*) echo "支持256色" ;;
dumb) echo "极简终端" ;;
*) echo "基础终端" ;;
esac
8. 总结
| 环节 | 要点 |
|---|---|
| 转义序列 | \033[ 开头,控制颜色/光标/屏幕 |
| 颜色 | 3x 前景、4x 背景、1 加粗,结尾重置 |
| 单行刷新 | \r 重写 + \033[2K 清行 |
| 进度条 | 循环内 \r 重绘,整数运算算填充 |
| 交互输入 | read -s 隐藏、-n 1 单键、-t 超时 |
| 信号清理 | trap cleanup 恢复光标与回显 |
| 多行 UI | \033[NA 上移重绘,tput 可移植 |
| 生产原则 | 非终端禁用颜色、函数化封装日志 |
终端 UI 让脚本从「黑箱运行」变成「可见、可控、可中断」:ANSI 控制码画界面,trap 保证中断不留残迹,-t 1 检测让输出在管道与 CI 里依旧干净。这套能力在部署发布脚本里特别有用——最后进入部署与发布脚本专题收尾。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。