<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>历史 · XB 现场笔记</title><link>https://www.xbcatm.com/zh/tags/%E5%8E%86%E5%8F%B2/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://www.xbcatm.com/zh/tags/%E5%8E%86%E5%8F%B2/index.xml" rel="self" type="application/rss+xml"/><item><title>bash history 是可以搜索的命令日志</title><link>https://www.xbcatm.com/zh/posts/linux-bash-history-session/</link><pubDate>Mon, 28 Feb 2028 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/zh/posts/linux-bash-history-session/</guid><description>Bash 历史便于复用命令，但会话内存、磁盘保存、过滤规则和显式导入共同决定哪些内容还能被搜索。</description><content:encoded>&lt;p&gt;命令历史能减少重复输入，却不是所有已执行操作的完整记录。Bash 通常在交互 Shell 中启用历史功能，配置还可以过滤条目或限制保留数量。运行中 Shell 的可搜索列表，与磁盘上的历史文件，是两个独立状态。理解这个区别，才能解释为什么某条命令在一个终端能找到，在另一个终端却没有，以及为什么意外结束会话可能丢掉尚未保存的记录。&lt;/p&gt;
&lt;h2 id="会话内存与历史文件不同"&gt;会话内存与历史文件不同&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;HISTSIZE&lt;/code&gt; 限制内存中的历史条目数量，&lt;code&gt;HISTFILESIZE&lt;/code&gt; 限制保存文件的行数。多行命令与时间戳记录会让行数不等于命令数，不能用简单的文件行数判断究竟保留了多少次操作。&lt;code&gt;HISTFILE&lt;/code&gt; 决定保存位置，通常指向用户的 Bash 历史文件。变量未设置或为空时不会正常保存；目标不可写时，当前会话仍可能查到记录，却没有对应的持久化副本。&lt;/p&gt;
&lt;p&gt;Bash 在参数展开之前、历史展开之后记录命令文本，并受到 &lt;code&gt;HISTCONTROL&lt;/code&gt; 与 &lt;code&gt;HISTIGNORE&lt;/code&gt; 的影响。因此，保存下来的变量引用并不一定显示原来执行时的变量值。当前目录、别名、函数、环境及外部文件，也都可能在下次复用命令之前发生变化。历史保存的是有用的输入，不是完整的可复现运行环境，更不是可以直接信任的操作审计证据。&lt;/p&gt;
&lt;h2 id="追加保存不会自动刷新其他终端"&gt;追加保存不会自动刷新其他终端&lt;/h2&gt;
&lt;p&gt;Bash 正常退出时保存历史，&lt;code&gt;histappend&lt;/code&gt; 决定追加到文件还是覆盖文件。如果此前没有主动保存，异常终止可能丢掉全部未写入的条目，不只是最后一条。&lt;code&gt;history -a&lt;/code&gt; 追加当前会话新输入、且尚未追加过的历史行。提前保存可以缩小丢失窗口，但不等于承诺任何系统故障之后都能完整恢复，更不能把一次文件写入等同于业务日志的可靠持久化。&lt;/p&gt;
&lt;p&gt;另一个仍在运行的 Shell 持有自己的列表，需要通过 &lt;code&gt;history -n&lt;/code&gt; 等操作读取新追加的行。仅仅启用追加选项，不会让另一个终端的搜索结果实时更新。&lt;code&gt;history -r&lt;/code&gt; 把整个文件内容追加到现有列表，重复运行可能制造重复条目；&lt;code&gt;history -w&lt;/code&gt; 则用当前列表替换文件，可能丢掉其他会话留下的记录。这些命令的方向和覆盖语义不同，不能当作几种等价的同步写法。&lt;/p&gt;
&lt;p&gt;如果把追加和导入接入 &lt;code&gt;PROMPT_COMMAND&lt;/code&gt;，还要保留已有提示符钩子，以及它们对上一条命令退出状态的使用。直接覆盖已有钩子可能破坏其他 Shell 行为。多个会话共享一个文件，也不会因此形成可信的全局执行顺序。需要顺序、退出码或责任归属时，应使用独立的操作日志，而不是从交错的历史文本中反推完整事实。&lt;/p&gt;
&lt;h2 id="搜索后先检查再执行"&gt;搜索后先检查，再执行&lt;/h2&gt;
&lt;p&gt;在常见的 Emacs 风格 Readline 绑定中，Ctrl-R 向后增量搜索，再按一次查找更早的匹配。应使用编辑按键退出搜索，先检查所选命令，再决定是否执行；Ctrl-G 可以取消搜索。回车会接受命令，Ctrl-J 也是换行，不能把它说成不会执行的预览键。其他编辑模式或自定义终端的绑定可能不同，应以当前配置为准。&lt;/p&gt;
&lt;p&gt;复用旧命令时，检查主机名、目录、选项和目标。上个月正确的命令，今天可能已经指向另一套环境。用辨识度高的片段搜索可以减少翻找，但找到之后仍应查看整行，不能把第一条匹配结果直接当成当前想做的操作。历史搜索解决的是找回输入的问题，不负责重新判断操作意图。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;命令消失可能来自过滤规则、保留上限、不同的历史文件、保存失败，或者当前会话没有导入新记录。应分别核对这些原因。&lt;code&gt;ignoredups&lt;/code&gt; 只排除连续重复，不会对整个历史全局去重；&lt;code&gt;ignoreboth&lt;/code&gt; 只是把它与忽略行首空格的规则组合，也不会得到一份全局唯一的命令档案。判断条目是否丢失之前，先明确当前启用了什么过滤条件。&lt;/p&gt;
&lt;p&gt;行首空格只有在相应配置生效时才会排除条目。它不会移除终端录制、进程参数或其他位置的日志，因此不能把凭据直接放进命令行，再依赖历史过滤保证保密。诊断示例也应使用合成条目和临时历史文件，避免为了证明历史功能可用，就把真实用户最近执行过的命令打印出来。&lt;/p&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;使用 Python 3、Bash 和常见 Unix 工具运行下面的完整示例。环境在 Bash 启动之前就把历史文件指向临时目录，整个过程只查看合成文本。花括号分组让控制命令先被整体读取，避免启用历史之后又把实验自身的控制步骤逐行写进列表。退出前解除自动保存目标，随后由临时目录管理器清理测试文件。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tempfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;set -eu
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;set -o history
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;history -c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;history -s &amp;#39;demo original&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;history -w &amp;#34;$HISTFILE&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;printf &amp;#39;first-file:\n&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;cat &amp;#34;$HISTFILE&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;history -c
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;history -s &amp;#39;demo replacement&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;history -w &amp;#34;$HISTFILE&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;printf &amp;#39;replacement-file:\n&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;cat &amp;#34;$HISTFILE&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;history -r &amp;#34;$HISTFILE&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;printf &amp;#39;memory-after-reread:\n&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;history | sed &amp;#39;s/^[[:space:]]*[0-9][0-9]*[[:space:]]*//&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;unset HISTFILE
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;}&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TemporaryDirectory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bash-history-demo-&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;history_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;directory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;history&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BASH_ENV&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HISTFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;history_path&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;HISTSIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;100&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;HISTFILESIZE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;100&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HISTCONTROL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HISTIGNORE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;HISTTIMEFORMAT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--noprofile&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;--norc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;script&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第一次写入后，文件只包含 &lt;code&gt;demo original&lt;/code&gt;；替换后只包含 &lt;code&gt;demo replacement&lt;/code&gt;。内存已经有替换条目，再读取文件，会看到两条相同内容。这组结果直接区分覆盖文件与向内存追加，并且不会执行保存的那些字符串。它没有把文件操作试验冒充键盘绑定验证或崩溃持久性测试；后两者需要在对应环境中单独观察。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://tiswww.case.edu/php/chet/bash/bashref.html#Bash-History-Facilities"&gt;Bash 历史设施&lt;/a&gt;，会话列表与历史文件的保存规则，已核验 2026-09-07。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tiswww.case.edu/php/chet/bash/bashref.html#Bash-History-Builtins"&gt;Bash 历史内建命令&lt;/a&gt;，追加、替换及导入条目的差异，已核验 2026-09-07。&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tiswww.case.edu/php/chet/bash/bashref.html#Commands-For-History"&gt;Readline 历史命令&lt;/a&gt;，搜索及接受命令的按键绑定，已核验 2026-09-07。&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>