用echo和环境变量实现日志分级需设LOG_LEVEL为数字级别(0=off,1=error…),用算术比较控制输出,配合printf封装带时间戳和前缀的log_error/log_warn等函数,并通过LOG_TO_FILE变量按需tee双写,避免子shell丢失变量、高频date调用和无差别debug刷屏。

如何用 echo 和环境变量实现轻量级日志级别控制
直接靠 Shell 内置命令就能做分级打印,不需要额外依赖。核心是用一个全局变量(比如 LOG_LEVEL)控制是否输出某条日志,再配合带前缀的 echo 封装函数。
常见错误是把级别写死在函数里,或者用 case 做字符串匹配——既慢又难调试。更稳妥的做法是用数字级别:0=off, 1=error, 2=warn, 3=info, 4=debug,用算术比较判断是否输出。
-
LOG_LEVEL推荐设为环境变量,方便启动时统一控制:LOG_LEVEL=3 ./script.sh - 函数名建议用
log_error、log_warn这类明确语义的,避免用log加参数方式——容易漏传或传错 - 所有日志统一加时间戳和级别标识,例如
[ERROR] 14:22:05 failed to bind port,便于 grep 和日志聚合
为什么 printf 比 echo 更适合封装日志函数
echo 对转义符(如 \n、\t)行为不一致:不同 Shell(bash/zsh/dash)甚至不同系统上的 echo -e 支持程度不同。而 printf 行为稳定,且支持格式化占位符,更适合构造结构化日志行。
比如想让 log_info 自动补上时间戳和前缀,用 printf 可以一行搞定:
log_info() {
[ "${LOG_LEVEL:-0}" -ge 3 ] || return
printf "[%s] %s %s\n" "INFO" "$(date '+%H:%M:%S')" "$*"
}
- 注意
"$*"而不是"$@":日志通常把整个参数当一条消息拼接,空格分隔更自然 -
$(date '+%H:%M:%S')比$(date +%s)更易读,且无时区干扰;高频打日志时可提前缓存时间戳减少 fork 开销 - 别在函数里调用
date多次——每个log_*函数都单独调一次,会明显拖慢脚本执行
怎么让日志同时输出到终端和文件而不卡住管道
用 tee 是最直接的方式,但要注意默认行为:如果目标文件不可写或磁盘满,tee 会失败并中断后续命令。生产脚本中必须加容错。
- 用
tee -a /var/log/myscript.log 2>/dev/null忽略写入失败,保证主流程不被阻断 - 不要用
exec > >(tee ...)全局重定向——它会让所有输出(包括read提示、交互输入)都被捕获,破坏交互性 - 更安全的做法是每个日志函数自己决定是否双写,例如加个开关:
LOG_TO_FILE=/var/log/myscript.log,函数内检测该变量是否存在再调tee
多级函数嵌套时怎么避免重复打印或丢失上下文
Shell 没有调用栈自动追踪,但可以用临时变量模拟简单上下文,比如记录当前模块名或任务阶段。常见错误是把 log_debug 无差别开到最高级,结果刷屏掩盖真正问题。
- 给每个子任务加独立日志前缀,例如
log_debug "net" "checking interface lo",函数内部拼成[DEBUG][net] ... - 避免在循环体里高频调用
log_debug,尤其当LOG_LEVEL=4时——每轮都date+printf+tee,I/O 成瓶颈 - 如果某个函数被反复调用(如轮询检查),建议只在状态变更时打日志,而不是每次进函数都打
真正的难点不在怎么打印,而在怎么让日志“说得清、找得准、扛得住”。比如 LOG_LEVEL 被子 shell 遗忘、date 调用被 strace 干扰、tee 缓冲导致日志延迟——这些细节不显眼,但线上出问题时第一个背锅。



















