如何删除 .bash_history 中的重复项,保留顺序?

cwd*_*cwd 82 command-line bash command-history sort uniq

我真的很喜欢使用control+r递归搜索我的命令历史记录。我发现了一些我喜欢与它一起使用的好选择:

# ignore duplicate commands, ignore commands starting with a space
export HISTCONTROL=erasedups:ignorespace

# keep the last 5000 entries
export HISTSIZE=5000

# append to the history instead of overwriting (good for multiple connections)
shopt -s histappend
Run Code Online (Sandbox Code Playgroud)

对我来说唯一的问题是erasedups只删除连续的重复项 - 所以使用这串命令:

ls
cd ~
ls
Run Code Online (Sandbox Code Playgroud)

ls命令实际上将被记录两次。我想过定期使用 cron 运行:

cat .bash_history | sort | uniq > temp.txt
mv temp.txt .bash_history
Run Code Online (Sandbox Code Playgroud)

这将实现删除重复项,但不幸的是顺序不会被保留。如果我不先sort处理文件,我不相信uniq可以正常工作。

如何删除 .bash_history 中的重复项,保留顺序?

额外学分:

.bash_history通过脚本覆盖文件有什么问题吗?例如,如果您删除了一个 apache 日志文件,我认为您需要发送一个 nohup / reset 信号kill以使其刷新它与文件的连接。如果.bash_history文件是这种情况,也许我可以用某种方式ps来检查并确保在运行过滤脚本之前没有连接的会话?

小智 71

因此,在被重复项惹恼后,我一直在寻找完全相同的东西,并发现如果我编辑我的~/.bash_profile或我~/.bashrc的:

export HISTCONTROL=ignoreboth:erasedups
Run Code Online (Sandbox Code Playgroud)

它完全符合您的要求,它只保留最新的任何命令。ignoreboth实际上就像做一样,ignorespace:ignoredups并且erasedups完成工作。

至少在我使用 bash 的 Mac 终端上,这项工作很完美。在 askubuntu.com 上找到它。

  • 这应该是正确答案 (14认同)
  • 这只会忽略重复的、连续的命令。如果您在两个给定的命令之间反复交替,您的 bash 历史记录将被重复填满 (14认同)
  • 这个答案包含有用的信息,但误导性地声称“完全按照你想要的做”。该问题指出“对我来说问题是擦除仅擦除连续的重复项”。这个答案仅解释如何使用erasedups来删除连续的重复项。它并不是如何删除所有重复项(而不仅仅是连续重复项)的实际问题的答案。 (3认同)

wnr*_*rph 41

对历史进行排序

此命令的工作方式类似于sort|uniq,但将线条保持在原位

nl|sort -k 2|uniq -f 1|sort -n|cut -f 2
Run Code Online (Sandbox Code Playgroud)

基本上,在每一行前面加上它的编号。sort|uniq-ing之后,所有行都根据它们的原始顺序(使用行号字段)重新排序,并且行号字段从行中删除。

这个解决方案有一个缺陷,即未定义一类相等线的哪个代表将使其在输出中,因此它在最终输出中的位置是未定义的。但是,如果应该选择最新的代表,您可以sort通过第二个键输入:

nl|sort -k2 -k 1,1nr|uniq -f1|sort -n|cut -f2
Run Code Online (Sandbox Code Playgroud)

管理 .bash_history

对于重读和写回历史,您可以分别使用history -ahistory -w

  • [decorate-sort-undecorate](http://en.wikipedia.org/wiki/Schwartzian_transform) 的一个版本,使用 shell 工具实现。好的。 (7认同)
  • 每行代码开头的“nl”是什么?难道不应该是“历史”吗? (3认同)

Cla*_*ley 28

在野外找到了这个解决方案并进行了测试:

awk '!x[$0]++'
Run Code Online (Sandbox Code Playgroud)

第一次看到一行的特定值 ($0) 时,x[$0] 的值为零。
零的值被反转!并变为一。
计算结果为 1 的语句会导致默认操作,即打印。

因此,第一次$0看到特定内容时,就会打印出来。

每次(重复) 的值x[$0]都会增加,
其否定值为零,并且不会打印计算结果为零的语句。

要保留最后一个重复值,请反转历史并使用相同的 awk:

awk '!x[$0]++' ~/.bash_history                 # keep the first value repeated.

tac ~/.bash_history | awk '!x[$0]++' | tac     # keep the last.
Run Code Online (Sandbox Code Playgroud)

  • 如果 .bash_history 条目位于两行(时间戳后跟命令本身),这种情况不会中断吗? (2认同)

小智 12

扩展克莱顿答案:

tac $HISTFILE | awk '!x[$0]++' | tac | sponge $HISTFILE
Run Code Online (Sandbox Code Playgroud)

tac反转文件,确保您已安装moreutils以便sponge可用,否则使用临时文件。

  • 啊哈,来自“man Sponge”:与 shell 重定向不同,Sponge 在写入输出文件之前吸收所有输入。这允许构建读取和写入同一文件的管道。 (4认同)
  • 对于 Mac 上的用户,请使用“brew install coreutils”,并注意所有 GNU utils 前面都有一个“g”,以避免与 BSD 内置 Mac 命令混淆(例如,gsed 是 GNU,而 sed 是 BSD)。所以使用`gtac`。 (2认同)

smi*_*rog 9

这是一篇旧帖子,但对于想要打开多个终端并在窗口之间同步历史记录但不重复的用户来说,这是一个永久的问题。

我在 .bashrc 中的解决方案:

shopt -s histappend
export HISTCONTROL=ignoreboth:erasedups
export PROMPT_COMMAND="history -n; history -w; history -c; history -r"
tac "$HISTFILE" | awk '!x[$0]++' > /tmp/tmpfile  &&
                tac /tmp/tmpfile > "$HISTFILE"
rm /tmp/tmpfile
Run Code Online (Sandbox Code Playgroud)
  • histappend 选项将缓冲区的历史添加到历史文件的末尾 ($HISTFILE)
  • ignoreboth 和 erasedups 防止在 $HISTFILE 中保存重复的条目
  • prompt 命令更新历史缓存
    • history -n 从 $HISTFILE 中读取自上次回车以来可能出现在不同终端中的所有行
    • history -w 将更新的缓冲区写入 $HISTFILE
    • history -c 擦除缓冲区,因此不会发生重复
    • history -r 重新读取 $HISTFILE,附加到现在的空白缓冲区
  • awk 脚本存储它遇到的每一行的第一次出现。tac反转它,然后反转它,以便它可以与历史记录中最新的命令一起保存
  • rm /tmp 文件

每次打开一个新的 shell 时,历史记录都会被擦除,每次您Enter在不同的 shell/终端窗口中按下键时,它都会从文件中更新此历史记录。

  • Erasedups 仅删除连续的重复项。您是正确的,awk 命令重复了eradupes 命令,使其变得多余。 (2认同)
  • bash 时间戳失败。大多数事情不考虑时间戳。请参阅我的解决方案。 (2认同)

Lri*_*Lri 6

这些将保留最后重复的行:

ruby -i -e 'puts readlines.reverse.uniq.reverse' ~/.bash_history
tac ~/.bash_history | awk '!a[$0]++' | tac > t; mv t ~/.bash_history
Run Code Online (Sandbox Code Playgroud)