Screen日志是非结构化字节流,含ANSI序列和控制字符,需用状态机逐字节解析;关键在于明确提取目标,而非通用解析方案。

Screen 日志本身不是结构化格式,C++ 没有内置解析器——你得自己按行读、按规则切、按语义识别。关键不是“怎么解析”,而是“你到底想从日志里抓什么”。
screen -L 生成的日志长什么样?
启用 screen -L 后,screen 会把终端所有输出(含控制字符、ANSI 转义序列、换行、回车、光标移动)原样写入 screenlog.0 等文件。它不是 JSON 或 CSV,而是一段带噪声的字节流:
^M^[[Khello world^M^[[Kdone.^M^[[K
其中 ^M 是 \r,^[[K 是 ANSI 清行序列(ESC[K)。直接用 std::getline 按 \n 切,会漏掉 \r 结尾的行;不清理 ANSI 序列,文本就含乱码。
- 日志默认不带时间戳,除非你用
logfile /path/to/log.$STAMP配合defhstatus on等 hack - 多窗口日志混在同一文件,无上下文标记(除非你手动
screen -S name并分离 log) - 二进制输出(如 cat 一个图片文件)会导致日志含 \0,
std::string构造可能提前截断
用 std::ifstream 逐字节读 + 状态机去噪
别幻想正则一发匹配。ANSI 序列长度不定(ESC[1;32m、ESC[?25l),且可能嵌套或中断。稳妥做法是边读边状态机过滤:
立即学习“C++免费学习笔记(深入)”;
- 遇到
\x1b(ESC)就进入“ANSI 模式”,跳过后续 [、数字、字母、分号直到字母结束 - 遇到
\r不丢弃,先缓存为“当前行暂存区”的起始位置;遇到\n再 flush 当前行(模拟终端换行逻辑) - 用
std::vector<char></char>替代std::string读原始字节,避免 \0 截断
示例片段(只处理基础清行和颜色):
std::vector<char> buf;
std::ifstream f("screenlog.0", std::ios::binary);
char c;
bool in_ansi = false;
while (f.get(c)) {
if (in_ansi) {
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '@') in_ansi = false;
continue;
}
if (c == '\x1b') { in_ansi = true; continue; }
if (c == '\r') { /* 记录行首偏移 */ continue; }
if (c == '\n') { /* 提交当前缓存行 */ continue; }
buf.push_back(c);
}grep 出来再喂给 C++ 更实际
多数场景你其实只关心含关键词的行(如 "ERROR"、"pid=123")。与其在 C++ 里重写 grep,不如用系统工具预处理:
-
grep -oE 'pid=[0-9]+' screenlog.0 | sort -u > pids.txt→ 提取唯一 pid -
awk '/START/{flag=1;next} /END/{flag=0;next} flag' screenlog.0→ 提取 START/END 间内容 - 用
popen("grep -n 'timeout' screenlog.0", "r")在 C++ 中调用,比自己解析快且稳
注意:screen 日志是追加写,fseek 到末尾再倒读容易卡在半截 ANSI 序列里;用 tail -n 100 配合管道更可靠。
time_t 和时区让时间提取变复杂
screen 日志本身不含时间戳。如果你启用了 logfile $HOME/screenlog.%Y%m%d-%H%M%S,那时间在文件名里——但要注意 %H%M%S 是本地时区,std::get_time 默认不处理时区,std::chrono::zoned_time C++20 才有。更常见的是在 screen 的 hardstatus 里硬编码时间,例如:
hardstatus string "%{= kG}[ %{G}%H %{g}][%= %{= kw}%?%-Lw%?%{r}(%{W}%n*%f%t%?(%u)%?%{r})%{w}%?%+Lw%?%?%= %{g}][%{B} %d/%m %{W}%c %{g}]"
这会在每行顶部打印时间,但它是动态更新的,日志里表现为多行覆盖(\r + \x1b[K)。提取时得识别 "%d/%m.*%c" 这类模式,并关联后续真实日志行——没有通用解法,必须按你的 hardstatus 格式定制正则。
真正难的从来不是读文件,而是定义清楚:哪段算“一条有效日志”,谁负责保证不丢字符,以及是否接受延迟(比如等 1 秒确认 \r\n 完整性)。这些决策比代码本身影响更大。


















