Linux下基于样例文件生成可轮转近实时访问日志时行3被跳过的原因及修复
日志行被跳过的核心原因
本质是awk的自动逐行触发逻辑和手动调用getline的读取逻辑发生了输入流竞争,具体执行链路和你给出的调试输出完全匹配:
- awk的默认执行规则是:每从输入文件读到一行内容,就会自动触发一次无前置匹配条件的主处理块,触发时会把刚读到的行赋值给内置变量
$0,同步更新行号、记录位置等状态。 - 你在主处理块内部主动调用了
getline函数:这个函数会主动从输入流读取下一行内容,直接覆盖当前的$0、行号等状态,且这个操作不会被awk的自动行触发流程感知。 - 对应样例的实际执行过程:
- awk首次从样例日志读入
line1,触发第一次主块执行 - 主块内j=1:取当前
$0(line1)做写入处理,之后执行getline主动读入line2,覆盖当前$0 - 主块内j=2:取当前
$0(line2)做写入处理,之后执行getline主动读入line3,覆盖当前$0 - 当前主块执行完毕,awk回到自动读行流程:不会使用刚才已经被
getline读到的line3触发下一次主块,而是直接从输入流的下一个位置读新行,也就是line4,触发第二次主块执行 - 已经被读到内存的
line3没有任何代码分支会处理它,直接被丢弃,最终造成行内容丢失。
- awk首次从样例日志读入
除此之外,原代码还存在一个统计偏差问题:else分支里用plb += length($0)计算新增日志字节数,但此时$0已经被后续的getline调用更新成了下一行内容,统计值和实际写入内容的长度不匹配,长期运行会导致文件大小判断逻辑完全失效。
修复方案
核心原则是不要在同一逻辑里混用awk自动读行、手动getline两种读取方式,二选一即可,以下是两种可落地的修改方向:
方案1:完全放弃awk自动逐行触发,全程用手动getline控制读取流程
把原来依赖自动触发的主块逻辑,移到BEGIN块中,手动打开样例日志文件,逐行读取处理,从根源上避免输入流错位:
BEGIN { # 打开外部传入的样例日志文件 sample_log = ARGV[1] event_count = 0 # 循环逐行读取样例日志,直到文件读完 while ( (getline log_line < sample_log) > 0 ) { event_count++ print "start batch of 2 events" print event_count % 2 print log_line # 保留原有的写入populated_log_file的逻辑,修正字节统计的取值 if ( plb >= 50 ) { close(plf) print log_line > plf plb = length(log_line) } else { print log_line >> plf plb += length(log_line) } # 每处理完2条事件,等待2秒 if (event_count % 2 == 0) { system("sleep 2") } } close(sample_log) }
修改后可去掉原来的while true外层循环,因为手动逐行读取不会跳行,处理完所有样例行后可以按需循环重读样例文件实现持续生成日志的效果。
方案2:仅触发一次主块,所有行读取靠手动getline完成
如果不想改动原有的shell外层循环结构,可以把原来的awk主块的触发条件改成只在第一次读行时执行,后续所有行的读取都靠主块内的getline完成,避免自动读行的干扰:
# 只在第一次读到文件的第一行时触发一次主块 NR == 1 { print "start batch of 2 events" for (j=1; j<=count; j++) { print j k = $0 print k if ( plb >= 50 ) { close(plf) print k > plf plb = length(k) } else { print k >> plf # 用当前写入的k计算长度,替换原来取值不准的$0 plb += length(k) } # 读完当前行后,主动读下一行 getline } system("sleep 2") }
这个改动里,因为主块只在NR==1时触发一次,后续不会再有自动读行的动作,所有的行读取都靠循环里的getline完成,不会出现跳行问题。
内容的提问来源于stack exchange,提问作者Noura
相关产品推荐
相关产品推荐

