HISTCONTROL=ignoredups是默认去重的最低门槛,仅忽略连续重复命令;要全局去重需显式设置erasedups,它会在新命令写入前删除历史中所有同内容旧记录。

HISTCONTROL=ignoredups 是默认去重的最低门槛
Linux 默认只对连续重复命令去重,比如连着敲两次 ls,第二次不会进 history;但中间插一条 pwd 后再敲 ls,它就又出现了。这不是 bug,是 bash 的原始设计逻辑——只看“上一条是否相同”。要真正按内容全局去重,必须显式设置 HISTCONTROL。
-
ignoredups:忽略与上一条完全相同的命令(默认已启用,但效果有限) -
erasedups:每次新命令写入前,先从整个历史缓冲区删掉所有同内容旧记录(推荐) -
ignorespace:忽略以空格开头的命令(常用于临时隐藏敏感操作) - 组合使用更实用:
export HISTCONTROL=erasedups:ignorespace
注意:erasedups 不影响 .bash_history 文件里已存在的重复项,只作用于当前会话新增命令。要清理已有文件里的重复,得手动去重:awk '!seen[$0]++' ~/.bash_history > /tmp/hist && mv /tmp/hist ~/.bash_history
为什么设置了 erasedups 还有重复?检查 PROMPT_COMMAND 是否覆盖了 history 写入
很多用户加了 erasedups 却发现终端关了再开,历史里还是有重复——问题往往出在 PROMPT_COMMAND 配置上。如果你用了类似 PROMPT_COMMAND='history -a' 实现即时保存,它会在每次提示符出现时把当前命令追加到文件,但不触发 erasedups 的清理逻辑(该逻辑只在命令执行后、加入缓冲区时生效)。
-
history -a是追加模式,不重读也不重排缓冲区,所以重复命令照样写进文件 - 更稳妥的做法是改用
history -n; history -w组合:先从文件读新条目(-n),再全量写回(-w),这样erasedups才能在内存中真正生效 - Zsh 用户不用操心这个,
SHARE_HISTORY+INC_APPEND_HISTORY天然兼容去重逻辑
zsh 和 bash 的去重行为差异很实在
别指望换 shell 就自动解决——zsh 默认不开启任何去重,HISTCONTROL 是 bash 专属变量,zsh 用的是完全不同的机制。
- bash:
HISTCONTROL=erasedups立即生效,但仅限当前会话缓冲区 - zsh:需显式启用
setopt HIST_IGNORE_ALL_DUPS(全局去重)或HIST_IGNORE_DUPS(仅忽略连续重复) - zsh 还多一个
HIST_EXPIRE_DUPS_FIRST:当历史满时,优先删旧重复项,留最新的一条 - 两者都支持
export HISTTIMEFORMAT="%F %T ",但 zsh 的时间戳默认不写入.zsh_history,需额外加EXTENDED_HISTORY
敏感命令漏进 history 的真实原因常被忽略
你以为加了 ignorespace 就万无一失?错。这个选项只在命令**执行前**检测首字符,一旦你敲完 mysql -u root -p 按回车,密码就可能被明文记进 history——因为密码不是命令参数的一部分,而是交互式输入,ignorespace 完全管不到。
- 真正安全的做法是:用
mysql --defaults-file=/path/to/my.cnf把凭据抽离,再给配置文件设chmod 600 - 或者启动子 shell:
(HISTSIZE=0; mysql -u root -p),退出即销毁该会话全部历史 -
unset HISTFILE是终极方案,但副作用明显:上下箭头失效、!!不能用、所有历史快捷键瘫痪
去重不是目的,关键是让历史记录“可信”——能快速翻到想要的命令,又不意外暴露不该留下的东西。最易被忽略的点是:erasedups 只清内存,不碰磁盘;history -w 是全量覆盖写入,不是增量追加;而 zsh 的 HIST_IGNORE_ALL_DUPS 在多终端并发写入时,仍可能因竞态出现短暂重复。


















