Hacker News 中文摘要

RSS订阅

追踪Zsh历史数据丢失的Bug -- Tracking down a Zsh history data loss bug

文章摘要

作者追踪并修复了一个Zsh历史记录数据丢失的bug。通过inotify、strace等工具定位问题,发现是多个Zsh进程同时写入历史文件时导致截断,并给出了解决方案。

文章总结

好的,这是根据您的要求,对原文主要内容进行的中文重述,保留了关键细节,并删减了与核心主题无关的内容(如附录中详细的AI评估表格和部分技术细节)。


追踪Zsh历史记录数据丢失的Bug

核心问题: 作者多年来偶尔发现,自己确信执行过的命令在Zsh历史记录文件(~/.zsh_history)中神秘消失。经过深入调查,最终定位并修复了一个存在约10年的数据丢失Bug。

好消息: Zsh 5.9.2版本(2026年7月12日发布)已修复此问题。

症状: 作者发现,有时历史记录文件中的条目只剩下非常陈旧的内容,而近几年的新记录全部丢失。文件本身没有损坏迹象,但行数不一致。作者不确定是Zsh自身、其他程序,还是多个Zsh进程共同导致的问题。

Zsh历史配置: 作者配置了INC_APPEND_HISTORY选项,使得每个shell会话都将命令实时追加到共享的历史文件中,但未启用SHARE_HISTORY(历史共享)。这意味着不同shell会话各自独立地向同一个历史文件写入。

追踪过程:

  1. inotify: 通过监控文件系统事件,作者发现Zsh并非直接修改历史文件,而是执行“读取旧文件 -> 写入新文件 -> 重命名新文件覆盖旧文件”的操作。但inotify无法显示执行操作的进程ID(PID)。

  2. fatrace: 使用fatrace工具,作者确认了执行读写操作的是Zsh进程本身,并获得了PID。但这仍无法揭示每个Zsh进程具体读写了多少数据。

  3. strace: 作者认为对每个交互式Zsh进程都附加strace追踪不切实际,且可能改变行为,因此未采用。

  4. bpftrace: 作者编写了bpftrace程序,监控Zsh对历史文件的所有系统调用。通过长期后台运行,作者捕获了一次历史记录被截断时的日志。关键发现是:在截断事件中,Zsh没有像正常情况那样读取文件直到末尾(read = 0),而是读取了较少的数据(11575296字节),然后写入了同样较少的数据(11572944字节),并用这个不完整的新文件替换了原文件。

定位Bug:

  • 制造崩溃: 为了在问题发生时获得更多信息,作者修改了Zsh 5.9.1的源代码,使其在写入的新历史文件行数少于50000行时主动崩溃。几天后,系统收集到了崩溃的核心转储文件。
  • 崩溃分析: 通过GDB分析核心转储,作者发现:
    • 崩溃发生在savehistfile函数中,它只写入了45546行历史记录。
    • 关键线索是errflag变量被设置为2(表示ERRFLAG_INT,即中断标志),且lasthist.interrupted变量为1
    • 这表明savehistfile在写入历史文件时,其内部的readhistfile函数被一个信号中断了。

根本原因:

  • 触发场景: 作者有在一天工作结束时,通过反复按Ctrl+D(退出会话)和Ctrl+C(中断)来关闭所有终端会话的习惯。当退出一个Zsh会话(Ctrl+D)时,Zsh会调用savehistfile来压缩历史文件。如果此时恰好有Ctrl+C信号(SIGINT)到达,就会中断savehistfile内部的readhistfile函数。
  • Bug所在: readhistfile函数在读取历史文件时,会检查中断标志(errflag & ERRFLAG_INT),如果收到信号会提前跳出读取循环。然而,savehistfile函数在退出时写入历史文件的过程中,没有检查中断标志。因此,当readhistfile被中断并返回一个不完整的历史记录后,savehistfile会忠实地将这个不完整的内容写入新文件,并覆盖掉完整的旧文件,从而导致数据丢失。

结论: 这个导致数据丢失的Bug在流行的Zsh shell中存在了约10年之久。作者非常高兴此问题最终在Zsh 5.9.2中得到修复。

评论总结

根据评论内容,总结如下:

主要观点与论据:

  1. Zsh历史记录丢失是常见问题(认可度:高)

    • 多位用户反映曾遭遇类似问题,如aclindsa表示“感觉丢失过zsh历史但未深究”,nubinetwork指出“bash中同时打开两个终端时,最后关闭的终端会覆盖历史记录”。
    • 关键引用:aclindsa "I have felt like I lost zsh history before but never looked into it";nubinetwork "That's a bug? Happens to me with bash all the time... whichever one I close last is the one that writes its history."
  2. 现有解决方案存在争议(认可度:中等)

    • 部分用户推荐替代工具,如fragmede建议使用atuin.sh;petters则采用sqlite数据库存储命令历史。
    • 关键引用:fragmede "Are people not just using atuin.sh these days?";petters "I store all commands executed in an sqlite database... The Bash history is useful for Ctrl+R"
  3. 对系统架构的批评(认可度:中等)

    • TZubiri批评默认2000条命令限制,认为“需要革命而非渐进优化”,并指出用户不应拥有删除自己命令历史的权限。
    • 关键引用:TZubiri "You can't expect much from a system that has those defaults... a revolution is needed, not incremental optimizations"
  4. 对调试工作的赞赏(认可度:高)

    • myshapeprotocol称赞“追踪shell历史内部数据丢失边缘案例需要绝对精确”,conferza表示“非常有趣的调查”。
    • 关键引用:myshapeprotocol "Tracking down subtle data loss edge cases in shell history internals requires absolute precision";conferza "A very interesting investigation"
  5. 替代方案建议(认可度:低)

    • pratyahava建议“将每个会话的命令历史写入单独文件,而非合并到一个文件”,认为这是更简单的文件系统方案。
    • 关键引用:pratyahava "why do this 'heroic' effort of 'cleverly' putting everything in one file... if we could just write command history of every session into a new separate file"

平衡性总结: - 多数用户认可问题存在且值得解决,但对解决方案存在分歧:部分支持现有修复,部分推荐第三方工具,部分主张根本性架构改革。 - 对调试工作的专业性和细致程度普遍表示赞赏,但对其实际效果存在不同看法。