在多个终端窗口中保留bash历史记录


526

我一直在打开多个终端。从2到10的任何位置,进行各种操作。现在,我们重新启动并打开另一组终端。有些人记得某些事情,有些人忘记了。

我想要一段历史:

  • 记住每个终端的一切
  • 可以从每个终端即时访问(例如,如果我ls在一个终端中,切换到另一个已经运行的终端,然后按向上,ls出现)
  • 如果命令前面有空格,请不要忘记命令。

我能做些什么来使bash更像那样工作?


57
我可以看到它的优点,但是就我个人而言,我会讨厌它。我通常在终端中打开3或4个选项卡以用于非常特定的用途:一个用于运行“ make”,一个用于vi,一个用于运行东西,等等。因此,当我编译时,我转到选项卡1,单击并显示“ make”。出现,依此类推。这对我非常有帮助。因此,如果突然我进入我的“ make”选项卡并点击弹出,并且出现一些随机的grep命令,我会很生气!虽然只是个人笔记
axel_c

4
@axel_c足够正确。在现有终端只能看到自己的历史记录而新终端却可以看到按时间顺序排列的准确命令列表时,我想不出一种明智的做法。
奥利2010年

8
@Oli写道:“当现有终端只能看到自己的历史记录而新终端却可以看到按时间顺序排列的准确命令列表时,我想不出一种明智的方法。” 怎么样(未经考验)export PROMPT_COMMAND="history -a; $PROMPT_COMMAND"。现有的外壳程序会将每个命令添加到历史记录文件中,以供新的外壳程序查看,但仅显示它们自己的历史记录。
克里斯·佩奇

2
您是否要全部存储历史记录或将所有历史记录合并到一个历史记录文件中?
kbyrd 2011年

3
简短的答案是:bash开发人员不打算使用它。基于冲洗然后重新读取历史记录的解决方案可能确实有效,但是要提防Shlemiel The Painter。简而言之:每个命令之间的处理工作量与历史记录大小成正比。
斯特凡纳·古里科

Answers:


329

将以下内容添加到〜/ .bashrc

# Avoid duplicates
export HISTCONTROL=ignoredups:erasedups  
# When the shell exits, append to the history file instead of overwriting it
shopt -s histappend

# After each command, append to the history file and reread it
export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"

20
此PROMPT_COMMAND解决方案的问题在于,每个历史记录项的编号在每个命令后都会更改:(。例如,如果键入history和1)ls 2)rm,则执行!1重复1,则历史记录编号可能会更改,并且可能会运行rm命令...
克里斯·金普顿

2
当我这样做时,当我在终端中发出命令之后直到我在终端中发出命令后,其他已经打开的终端才没有上次输入的命令-这是预期的吗?如果是这样,是否有办法立即真正修改其他终端的历史记录?
Suan

7
@Suan,根据命令,这对我来说似乎是正确的。我发现我们可以发出空命令(只需按Enter键)以更新历史记录。
sage 2013年

3
其实history -a(...)不会触发删除重复根据这个问题的答案“ignoredups”和“erasedups”设置与会话之间共同的历史冲突:猛砸历史。该答案还给出了history -<option>HISTCONTROL=ignoredups:erasedups设置配合使用的命令序列。
Piotr Dobrogost '16

23
没有理由exportHISTCONTROLPROMPT_COMMAND变量:您在定义他们.bashrc,使他们在每一个壳(即使是在非交互式的,这也是浪费)来定义。
支石墓

248

所以,这就是我所有与历史有关的.bashrc事情:

export HISTCONTROL=ignoredups:erasedups  # no duplicate entries
export HISTSIZE=100000                   # big big history
export HISTFILESIZE=100000               # big big history
shopt -s histappend                      # append to history, don't overwrite it

# Save and reload the history after each command finishes
export PROMPT_COMMAND="history -a; history -c; history -r; $PROMPT_COMMAND"

在Mac OS X 10.5上使用bash 3.2.17,在10.6上使用bash 4.1.7进行了测试。


3
嗯。由于命令号会在每次提示时更改,因此这会破坏使用$!34的能力。是否有解决方法@Davide @Schof @kch?

5
使用它来获取无限的历史: bash永恒的历史。但是,这是对上面代码的补充,因为它不会自动重新加载。

7
仅供参考,此处提到的解决方案都不能解决以下问题。我有两个shell窗口A和B。在shell窗口A中,我运行sleep 9999,并且(无需等待睡眠完成)在shell窗口B中,我希望能够sleep 9999在bash历史记录中看到。
pt

1
@pts我也以实时行为为目标,但是后来我意识到,拥有终端特定的历史记录会更方便,因为这些历史记录使在不同终端中处理不同事物的工作变得更加轻松。我发现这非常有用:stackoverflow.com/questions/338285/#answer-7449399基于此,我给自己起了一个别名href,该别名可以立即刷新当前终端的历史记录并在此过程中清理历史记录文件。每当我打开新终端时,都会在我的bashrc文件中执行该清理/同步操作,因此新终端具有最新的历史记录。我正在将其与history -a
trusktr 2012年

6
没有理由使用export这些变量:您正在定义它们,.bashrc因此它们将在每个外壳中定义(即使在非交互式外壳中也是如此,这也是很浪费的)
dolmen

118

这是我尝试的Bash会话历史记录共享。这将使bash会话之间的历史记录共享成为可能,这样就不会混淆历史记录计数器,并且可以像!number在某些约束条件下那样进行历史记录扩展。

在Ubuntu 10.04 LTS(Lucid Lynx)下使用Bash版本4.1.5。

HISTSIZE=9000
HISTFILESIZE=$HISTSIZE
HISTCONTROL=ignorespace:ignoredups

_bash_history_sync() {
    builtin history -a         #1
    HISTFILESIZE=$HISTSIZE     #2
    builtin history -c         #3
    builtin history -r         #4
}

history() {                  #5
    _bash_history_sync
    builtin history "$@"
}

PROMPT_COMMAND=_bash_history_sync

说明:

  1. 将刚输入的行添加到$HISTFILE(默认为.bash_history)。这将导致$HISTFILE增长一行。

  2. 将特殊变量设置$HISTFILESIZE为某个值将导致Bash 通过删除最旧的条目而截断$HISTFILE长度不超过$HISTFILESIZE行。

  3. 清除正在运行的会话的历史记录。这将减少历史记录计数器的数量$HISTSIZE

  4. 阅读的内容$HISTFILE并将其插入当前正在运行的会话历史记录中。这将使历史记录计数器增加中的行数$HISTFILE。请注意,行数$HISTFILE不一定是$HISTFILESIZE

  5. history()功能将覆盖内置历史记录,以确保在显示历史记录之前已对其进行同步。这对于按数字扩展历史记录是必需的(稍后将对此进行更多介绍)。

更多说明:

  • 步骤1确保当前运行会话中的命令被写入全局历史文件。

  • 步骤4确保将来自其他会话的命令读入当前会话历史记录。

  • 因为步骤4将增加历史记录计数器,所以我们需要以某种方式减少计数器。这是在步骤3中完成的。

  • 在步骤3中,历史记录计数器减少$HISTSIZE。在步骤4中,历史记录计数器将增加中的行数$HISTFILE。在第2步中,我们确保的行数$HISTFILE完全相同$HISTSIZE(这意味着该行$HISTFILESIZE必须与相同$HISTSIZE)。

关于历史扩展的约束:

使用按数字扩展历史记录时,应始终在使用数字之前立即查找该数字。这意味着在查找数字和使用数字之间不会显示任何提示信息。这通常意味着没有回车,也没有ctrl + c。

通常,一旦您有多个Bash会话,就无法保证历史记录按数字扩展将在两个Bash提示显示之间保留其值。因为当PROMPT_COMMAND执行时,所有其他Bash会话的历史记录都集成在当前会话的历史记录中。如果其他bash会话具有新命令,则当前会话的历史记录号将不同。

我认为这种约束是合理的。无论如何,我每次都必须查找该数字,因为我不记得任意的历史数字。

通常我会用数字来扩展历史记录

$ history | grep something #note number
$ !number

我建议使用以下Bash选项。

## reedit a history substitution line if it failed
shopt -s histreedit
## edit a recalled history line before executing
shopt -s histverify

奇怪的错误:

运行通过管道传输到任何内容的历史记录命令将导致该命令在历史记录中两次列出。例如:

$ history | head
$ history | tail
$ history | grep foo
$ history | true
$ history | false

所有内容都会在历史记录中列出两次。我不知道为什么。

改进思路:

  • 修改函数_bash_history_sync(),使其不会每次都执行。例如,它不应CTRL+C在出现提示后执行。CTRL+C当我决定不想执行该命令行时,经常会丢弃该命令行。有时我不得不CTRL+C停止Bash完成脚本。

  • 当前会话中的命令应始终是当前会话历史中的最新命令。这也将产生副作用,即给定的历史记录编号将保留其在此会话中的历史记录条目的值。


1
为什么不使用“ history -n”(尚未加载的重新加载行)而不是“ history -c; history -r”?
格雷厄姆

@Graham:我不想使用history -n它,因为它弄乱了历史计数器。另外,我发现history -n它也不可靠。
lesmana 2011年

1
缺点之一:具有多行字符串的命令通常仍保留在当前会话中。通过此技巧,它们可以立即分成几行。对-c -r使用-n不会有所帮助,cmdhist或lithist都不会。我认为目前尚无解决方法。
裘利斯

24
在尝试了一下之后,我实际上发现仅在history -a不使用-c和的情况下运行-r在可用性方面会更好(尽管这不是问题所在)。这意味着您运行的命令即使在退出当前Shell之前也可以立即在新Shell中使用,而在同时运行的Shell中则不可用。这样,Arrow-Up仍然始终选择当前会话的上次运行命令,我发现混乱程度要小得多。
Jo Liss'2

非常好的答案,它的工作原理与常见的“ history -a; history -n”不同
RichVel 2013年

42

我不知道使用的任何方式bash。但这是的最流行功能之一zsh
我个人更喜欢zshbash所以我建议尝试一下。

这是我.zshrc处理历史的部分:

SAVEHIST=10000 # Number of entries
HISTSIZE=10000
HISTFILE=~/.zsh/history # File
setopt APPEND_HISTORY # Don't erase history
setopt EXTENDED_HISTORY # Add additional data to history like timestamp
setopt INC_APPEND_HISTORY # Add immediately
setopt HIST_FIND_NO_DUPS # Don't show duplicates in search
setopt HIST_IGNORE_SPACE # Don't preserve spaces. You may want to turn it off
setopt NO_HIST_BEEP # Don't beep
setopt SHARE_HISTORY # Share history between session/terminals


16

为此,您需要在:中添加两行~/.bashrc

shopt -s histappend
PROMPT_COMMAND="history -a;history -c;history -r;$PROMPT_COMMAND"

来自man bash

如果启用了histappend shell选项(请参阅下面的SHELL BUILTIN COMMANDS下的shopt的说明),则这些行将追加到历史文件中,否则历史文件将被覆盖。


10

您可以编辑BASH提示符以运行Muerr建议的“ history -a”和“ history -r”:

savePS1=$PS1

(以防万一,几乎可以保证)

PS1=$savePS1`history -a;history -r`

(请注意,这些是反引号;它们将在每个提示上运行history -a和history -r。由于它们不输出任何文本,因此您的提示将保持不变。

一旦按照需要设置了PS1变量,就将其永久设置为〜/ .bashrc文件。

如果要在测试时返回到原始提示,请执行以下操作:

PS1=$savePS1

我已经对此进行了基本测试,以确保它能正常工作,但是不能history -a;history -r在每次提示时运行都会产生任何副作用。


2
kch的解决方案比我的解决方案更好。我现在在我的.bashrc中使用他的解决方案。

9

如果您需要同时解决以下问题的bash或zsh历史记录同步解决方案,请在http://ptspts.blogspot.com/2011/03/how-to-automatically-synchronize-shell.html中查看

问题如下:我有两个Shell窗口A和B。在Shell窗口A中,我运行sleep 9999,并且(无需等待睡眠完成)在Shell窗口B中,我希望能够sleep 9999在bash历史记录中看到。

此处大多数其他解决方案无法解决此问题的原因是,它们仅使用命令完成后才使用PROMPT_COMMAND或将它们的历史更改写入历史文件PS1,而这两个文件执行得太晚sleep 9999了。


1
这是一个不错的解决方案,但是我有几个问题。1.我可以使用原始的.bash_history文件吗,我不希望我的$ HOME中存在另一个bash历史文件。2.也许您应该考虑为此设置一个github存储库。
weynhamz 2012年

似乎调试钩子与bashdb冲突,每次我启动bash会话时,都会遵循follow输出。bash调试器bashdb,版本4.2-0.8版权所有2002、2003、2004、2006、2007、2008、2009、2010、2011 Rocky Bernstein这是免费软件,受GNU通用公共许可证保护,欢迎您使用。在某些条件下进行更改和/或分发其副本。**内部调试错误_Dbg_is_file():文件参数为null bash:_Dbg_filenames [$ fullname]:坏数组下标```
weynhamz 2012年

@Techlive Zheng:1.故意不支持原始.bash_history,因为.merged_bash_history使用不同的文件格式,因此,如果.merged_bash_history无法正确加载,bash不会意外破坏累积的历史记录。设计稳健,将保持原样。2. github回购通常是一个好主意,但是我没有时间为这个项目维护它,所以我没有这样做。-是的,它与bashdb冲突,并且没有简单的解决方案(它们使用相同的钩子)。我不打算进行修复,但是我接受补丁。

好的谢谢。我想出了一个更简单,更好的方法。
weynhamz 2012年

@TechliveZheng:能否与我们分享您简单而更好的解决方案,以便我们都能从中学习?(如果是这样,请添加问题的答案。)
pts

8

您可以用于history -a将当前会话的历史记录追加到历史文件,然后history -r在其他终端上使用来读取历史文件。 


8

是的,所以最后这让我很生气,找到了一个不错的解决方案:

# Write history after each command
_bash_history_append() {
    builtin history -a
}
PROMPT_COMMAND="_bash_history_append; $PROMPT_COMMAND"

这样做是将本线程中所说的内容混为一谈,只是我不明白为什么您会在每次命令后重新加载全局历史记录。我很少关心其他终端上会发生什么,但是我总是运行一系列命令,例如在一个终端上:

make
ls -lh target/*.foo
scp target/artifact.foo vm:~/

(简化示例)

而在另一个:

pv ~/test.data | nc vm:5000 >> output
less output
mv output output.backup1

我绝对不想共享命令


1
您在每个命令之后重新加载历史记录的原因是,这是问题所要求的行为(因此,这实际上并不能回答所提出的问题)。
Michael Homer 2015年

2
@MichaelHomer公平点。可以随意投票以使答案始终落在最底层,但是,我会将此归咎于OP,但并没有意识到所请求的行为有多糟糕,并且这是一个非常容易质疑的问题。
Yarek T 2015年

3
是的,这是一个公平的折衷。我的主要抱怨是我正在失去历史。即使这并不意味着在同时进行的会话中进行即时更新,这也应该停止这样做。
奥利2015年

7

这是我使用的替代方法。这很麻烦,但是它解决了@axel_c提到的问题,有时您可能希望在每个终端中有一个单独的历史记录实例(一个用于make,一个用于监视,一个用于vim,等等)。

我保留了一个单独的附加历史记录文件,并不断更新。我将以下内容映射到热键:

history | grep -v history >> ~/master_history.txt

这会将来自当前终端的所有历史记录追加到您的主目录中的一个名为master_history.txt的文件中。

我还有一个单独的热键可以搜索主历史文件:

cat /home/toby/master_history.txt | grep -i

我用猫 grep,因为它将光标留在末尾以输入我的正则表达式。一个不太丑陋的方法是在您的路径中添加几个脚本来完成这些任务,但是热键可以满足我的目的。我还将定期从其他工作主机上提取历史记录,并将该历史记录附加到我的master_history.txt文件中。

能够快速搜索并找到您使用过的棘手的正则表达式或7个月前想到的奇怪的perl单行代码总是很高兴的。


6

我可以为最后一个提供修复程序:确保环境变量HISTCONTROL不指定“ ignorespace”(或“ ignoreboth”)。

但是我对同时进行多个会话感到很痛苦。用bash处理起来根本不好。


6

这是我对@lesmana的回答的增强。主要区别在于并发窗口不共享历史记录。这意味着您可以继续在窗口中工作,而不必将其他窗口的上下文加载到当前窗口中。

如果您明确键入“历史记录”,或者如果您打开一个新窗口,则可以从以前的所有窗口中获取历史记录。

另外,我使用这种策略来存档机器上曾经键入的每个命令。

# Consistent and forever bash history
HISTSIZE=100000
HISTFILESIZE=$HISTSIZE
HISTCONTROL=ignorespace:ignoredups

_bash_history_sync() {
  builtin history -a         #1
  HISTFILESIZE=$HISTSIZE     #2
}

_bash_history_sync_and_reload() {
  builtin history -a         #1
  HISTFILESIZE=$HISTSIZE     #2
  builtin history -c         #3
  builtin history -r         #4
}

history() {                  #5
  _bash_history_sync_and_reload
  builtin history "$@"
}

export HISTTIMEFORMAT="%y/%m/%d %H:%M:%S   "
PROMPT_COMMAND='history 1 >> ${HOME}/.bash_eternal_history'
PROMPT_COMMAND=_bash_history_sync;$PROMPT_COMMAND

5

我选择将历史记录放入每个文件中,因为多个人可以在同一服务器上工作-分开每个会话的命令可以使审核更加容易。

# Convert /dev/nnn/X or /dev/nnnX to "nnnX"
HISTSUFFIX=`tty | sed 's/\///g;s/^dev//g'`
# History file is now .bash_history_pts0
HISTFILE=".bash_history_$HISTSUFFIX"
HISTTIMEFORMAT="%y-%m-%d %H:%M:%S "
HISTCONTROL=ignoredups:ignorespace
shopt -s histappend
HISTSIZE=1000
HISTFILESIZE=5000

历史现在看起来像:

user@host:~# test 123
user@host:~# test 5451
user@host:~# history
1  15-08-11 10:09:58 test 123
2  15-08-11 10:10:00 test 5451
3  15-08-11 10:10:02 history

文件看起来像:

user@host:~# ls -la .bash*
-rw------- 1 root root  4275 Aug 11 09:42 .bash_history_pts0
-rw------- 1 root root    75 Aug 11 09:49 .bash_history_pts1
-rw-r--r-- 1 root root  3120 Aug 11 10:09 .bashrc

3

在这里,我将指出一个问题

export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"

PROMPT_COMMAND="$PROMPT_COMMAND;history -a; history -n"

如果运行源〜/ .bashrc,则$ PROMPT_COMMAND将类似于

"history -a; history -c; history -r history -a; history -c; history -r"

"history -a; history -n history -a; history -n"

每当您运行“ source〜/ .bashrc”时,都会发生此重复。您可以在每次运行“ source〜/ .bashrc”后通过运行“ echo $ PROMPT_COMMAND”来检查PROMPT_COMMAND。

您可能会看到一些命令显然已损坏:“ history -n history -a”。但是好消息是它仍然有效,因为其他部分仍然形成了有效的命令序列(由于重复执行一些命令,这会涉及一些额外的费用。而且不是那么干净。)

我个人使用以下简单版本:

shopt -s histappend
PROMPT_COMMAND="history -a; history -c; history -r"

具有大多数功能,而没有上述问题。

值得一提的是:真的没有魔术。PROMPT_COMMAND只是一个普通的bash环境变量。在收到bash提示($符号)之前,将执行其中的命令。例如,您的PROMPT_COMMAND是“ echo 123”,并且您在终端中运行“ ls”。效果就像运行“ ls; echo 123”。

$ PROMPT_COMMAND="echo 123"

输出(就像运行'PROMPT_COMMAND =“ echo 123”; $ PROMPT_COMMAND'一样):

123

运行以下命令:

$ echo 3

输出:

3
123

“ history -a”用于将内存中的历史记录命令写入〜/ .bash_history

“ history -c”用于清除内存中的历史记录命令

“ history -r”用于从〜/ .bash_history读取历史命令到内存

请在此处查看历史记录命令说明:http : //ss64.com/bash/history.html

PS:正如其他用户所指出的,导出是不必要的。请参阅:在.bashrc中使用export


2

我编写了一个脚本,用于根据以下内容为每个会话或任务设置历史记录文件。

        # write existing history to the old file
        history -a

        # set new historyfile
        export HISTFILE="$1"
        export HISET=$1

        # touch the new file to make sure it exists
        touch $HISTFILE
        # load new history file
        history -r $HISTFILE

不必保存每个历史记录命令,但可以保存我关心的历史记录,并且可以轻松地通过每个命令检索它们。我的版本还列出了所有历史记录文件,并提供了全部搜索功能。

全文:https//github.com/simotek/scripts-config/blob/master/hiset.sh


2

这是一个解决方案,不会混淆各个会话的历史记录!

基本上,必须分别存储每个会话的历史记录,并在每个提示符下重新创建它。是的,它使用了更多的资源,但并没有听起来那么慢-仅当您有超过100000个历史条目时,延迟才开始显着。

这是核心逻辑:

# on every prompt, save new history to dedicated file and recreate full history
# by reading all files, always keeping history from current session on top.
update_history () {
  history -a ${HISTFILE}.$$
  history -c
  history -r
  for f in `ls ${HISTFILE}.[0-9]* | grep -v "${HISTFILE}.$$\$"`; do
    history -r $f
  done
  history -r "${HISTFILE}.$$"
}
export PROMPT_COMMAND='update_history'

# merge session history into main history file on bash exit
merge_session_history () {
  cat ${HISTFILE}.$$ >> $HISTFILE
  rm ${HISTFILE}.$$
}
trap merge_session_history EXIT

有关完整的解决方案,请参见本要点,包括一些防护措施和性能优化。


1

这适用于ZSH

##############################################################################
# History Configuration for ZSH
##############################################################################
HISTSIZE=10000               #How many lines of history to keep in memory
HISTFILE=~/.zsh_history     #Where to save history to disk
SAVEHIST=10000               #Number of history entries to save to disk
#HISTDUP=erase               #Erase duplicates in the history file
setopt    appendhistory     #Append history to the history file (no overwriting)
setopt    sharehistory      #Share history across terminals
setopt    incappendhistory  #Immediately append to the history file, not just when a term is killed

不幸的是,问题仅是针对bash的:-)
Jaleks

1
当我也搜索zsh时,它在Google上的第一个结果...这可能会有所帮助
Mulki

3
您应该问一个新问题,〜“在多个终端窗口中保留zsh历史记录”,前提是尚不存在。如果这是一个好问题,则完全可以(甚至可以鼓励)回答您自己的问题。
奥利

1

我一直很想这样做,特别是希望能够通过在新项目中重新运行命令的位置来检索命令(或通过命令查找目录)。因此,我将这个工具放到了一起,它将以前的解决方案(用于存储全局CLI历史记录)与称为percol(映射到C ^ R)的交互式grepping 工具结合在一起。在我开始使用它的第一台机器上,它仍然很光滑,现在具有超过2年的CLI历史。

就箭头键而言,它不会与本地CLI历史记录混淆,但可以让您轻松访问全局历史记录(您还可以映射到C ^ R以外的其他内容)


这是给zsh吗?
sjas

1
是的,我最初是为zsh制作的。虽然也可以用于打鱼和鱼。让我知道它是否有效。我有一段时间没有更改它了,我不确定我的安装说明中有多清晰
Gordon Wells


-1

这是我的.bashrc中的代码段,并在需要时提供简短说明:

# The following line ensures that history logs screen commands as well
shopt -s histappend

# This line makes the history file to be rewritten and reread at each bash prompt
PROMPT_COMMAND="$PROMPT_COMMAND;history -a; history -n"
# Have lots of history
HISTSIZE=100000         # remember the last 100000 commands
HISTFILESIZE=100000     # start truncating commands after 100000 lines
HISTCONTROL=ignoreboth  # ignoreboth is shorthand for ignorespace and     ignoredups

HISTFILESIZE和HISTSIZE是个人喜好,您可以根据自己的喜好进行更改。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.