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

Oli*_*Oli 633 bash command-history

我一直有不止一个终端打开。从两个到十个,做各种各样的点点滴滴。现在假设我重新启动并打开另一组终端。有些人记得某些事情,有些人忘记了。

我想要一段历史:

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

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

小智 401

将以下内容添加到您的~/.bashrc:

# Avoid duplicates
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
PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"
Run Code Online (Sandbox Code Playgroud)

  • 没有理由`export``HISTCONTROL` 和`PROMPT_COMMAND` 变量:你在`.bashrc` 中定义它们,所以它们将在每个shell 中定义(即使在非交互式shell 中,这也是浪费)。 (33认同)
  • 这个 PROMPT_COMMAND 解决方案的问题是每个命令后每个历史项目的数字都会改变:(。例如,如果你输入历史和 1) ls 2) rm,那么你执行 !1 来重复 1,历史数字可能会改变并且可能会运行 rm 命令... (31认同)
  • @Suan,根据命令,这对我来说似乎是正确的。我发现我们可以发出一个空命令(只需按回车键)来更新历史记录。 (9认同)
  • 实际上 *`history -a` (...) 根据 [this](http://unix.stackexchange.com/a/18443/5355) 对问题的回答 [Bash history: “ignoredups ”和“erasedups”设置与跨会话的公共历史记录冲突](http://unix.stackexchange.com/q/18212/5355)。此答案还提供了与“HISTCONTROL=ignoredups:erasedups”设置配合使用的“history -<option>”命令序列。 (5认同)
  • 当我这样做时,其他已经打开的终端在我按下“向上”之前没有最后输入的命令,直到我在 _that_ 终端中发出命令 - 这是预期的吗?如果是这样,有没有办法真正立即修改其他终端的历史记录? (3认同)
  • 如果您已经进行了擦除,我认为没有必要设置 ignoreups ? (2认同)

小智 279

所以,这就是我所有与历史相关的.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"
Run Code Online (Sandbox Code Playgroud)

在 Mac OS X 10.5 上用 bash 3.2.17 测试,在 10.6 上用 bash 4.1.7 测试。

  • 没有理由`export`变量:你在`.bashrc`中定义它们,所以它们将在每个shell中定义(即使是在非交互式shell中,这也是浪费) (15认同)
  • 仅供参考 这里提到的解决方案都不能解决以下问题。我有两个 shell 窗口 A 和 B。在 shell 窗口 A 中,我运行 `sleep 9999`,并且(无需等待睡眠完成)在 shell 窗口 B 中,我希望能够在 bash 中看到 `sleep 9999`历史。 (11认同)
  • 嗯.. 这会杀死使用 $ !34 的能力,因为命令编号在每个提示中都会改变。@Davide @Schof @kch 有解决方法吗? (6认同)
  • 使用它可以获得无限历史:[bash 永恒历史](http://www.debian-administration.org/articles/543)。然而,这是对上面代码的补充,因为它不会自动重新加载。 (5认同)
  • @pts 我也瞄准了实时行为,但后来我意识到拥有终端特定的历史记录会更方便,这使得在不同终端中处理不同的事情变得更容易。我发现这非常有用:http://stackoverflow.com/questions/338285/#answer-7449399 基于此,我为自己创建了一个名为“href”的别名,它可以立即刷新当前终端的历史记录并清理进程中的历史文件。每当我打开一个新终端时,都会在我的 bashrc 文件中执行清理/同步,以便新终端具有最新的历史记录。我将其与“history -a”一起使用 (2认同)
  • 你让我今天一整天都感觉很好! (2认同)

les*_*ana 131

这是我对 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
Run Code Online (Sandbox Code Playgroud)

解释:

  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)。

关于历史扩展的约束:

使用数字历史扩展时,您应该始终在使用前立即查找数字。这意味着在查找号码和使用号码之间不会显示 bash 提示。这通常意味着没有输入和没有 ctrl+c。

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

我觉得这个限制是合理的。反正我每次都得查这个数字,因为我记不起任意的历史数字。

通常我使用这样的数字扩展历史

$ history | grep something #note number
$ !number
Run Code Online (Sandbox Code Playgroud)

我建议使用以下 Bash 选项。

## reedit a history substitution line if it failed
shopt -s histreedit
## edit a recalled history line before executing
shopt -s histverify
Run Code Online (Sandbox Code Playgroud)

奇怪的错误:

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

$ history | head
$ history | tail
$ history | grep foo
$ history | true
$ history | false
Run Code Online (Sandbox Code Playgroud)

都将被列入历史两次。我不知道为什么。

改进思路:

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

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

  • 在尝试了一段时间之后,我实际上发现只运行 `history -a`,没有 `-c` 和 `-r`,在可用性方面更好(尽管这不是问题所问的)。这意味着即使在退出当前 shell 之前,您运行的命令在新 shell 中立即可用,但不能在并发运行的 shell 中使用。这样,Arrow-Up 仍然总是选择*当前会话*的上次运行命令,我觉得这不那么令人困惑。 (30认同)
  • 为什么不使用“history -n”(重新加载尚未加载的行)而不是“history -c;history -r”? (2认同)
  • 缺点之一:具有多行字符串的命令通常仍保留在当前会话中。通过这个技巧,它们立即被分成单独的行。使用 -n 代替 -c -r 没有帮助,cmdhist 或 lithist 也没有帮助。我认为目前没有解决方法。 (2认同)

Mac*_*tka 52

我不知道以任何方式使用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
Run Code Online (Sandbox Code Playgroud)


Chr*_*own 18

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

shopt -s histappend
PROMPT_COMMAND="history -a;history -c;history -r;$PROMPT_COMMAND"
Run Code Online (Sandbox Code Playgroud)

来自man bash

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


小智 13

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

savePS1=$PS1
Run Code Online (Sandbox Code Playgroud)

(万一你搞砸了,这几乎是可以保证的)

PS1=$savePS1`history -a;history -r`
Run Code Online (Sandbox Code Playgroud)

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

按照您想要的方式设置 PS1 变量后,将其永久设置在您的 ~/.bashrc 文件中。

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

PS1=$savePS1
Run Code Online (Sandbox Code Playgroud)

我已经对此进行了基本测试以确保它可以正常工作,但无法说明history -a;history -r在每个提示下运行的任何副作用。

  • kch 的解决方案比我的好。我现在在我的 .bashrc 中使用他的解决方案。 (3认同)

pts*_*pts 11

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

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

此处的大多数其他解决方案无法解决此问题的原因是,他们正在使用PROMPT_COMMANDor将历史更改写入历史文件PS1,这两者都执行得太晚,只有在sleep 9999命令完成后。


Yar*_*k T 11

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

# Write history after each command
_bash_history_append() {
    builtin history -a
}
PROMPT_COMMAND="_bash_history_append; $PROMPT_COMMAND"
Run Code Online (Sandbox Code Playgroud)

这样做是对该线程中所说内容的某种合并,除了我不明白为什么要在每个命令后重新加载全局历史记录。我很少关心其他终端中发生的事情,但我总是运行一系列命令,比如在一个终端中:

make
ls -lh target/*.foo
scp target/artifact.foo vm:~/
Run Code Online (Sandbox Code Playgroud)

(简化示例)

而在另一个:

pv ~/test.data | nc vm:5000 >> output
less output
mv output output.backup1
Run Code Online (Sandbox Code Playgroud)

我不希望共享命令

  • 您在每个命令后重新加载历史记录的原因是,这是问题所需的行为(因此这实际上并没有回答所提出的问题)。 (3认同)
  • 在这里,是的,这是一个公平的妥协。我的主要抱怨是我正在失去历史。这应该会阻止这种情况,即使这并不意味着跨同时会话的即时更新。 (3认同)
  • @MichaelHomer 公平点。随意投反对票,以便答案保持在底部,但是,我会将其归结为 OP,但没有意识到所请求的行为会有多糟糕,并且事实上这是一个非常适合谷歌搜索的问题。 (2认同)

小智 10

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


Tob*_*oby 10

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

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

history | grep -v history >> ~/master_history.txt
Run Code Online (Sandbox Code Playgroud)

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

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

cat /home/toby/master_history.txt | grep -i
Run Code Online (Sandbox Code Playgroud)

我用猫 | grep 因为它将光标留在最后以输入我的正则表达式。一个不那么难看的方法是在你的路径中添加几个脚本来完成这些任务,但热键可以满足我的目的。我还会定期从我使用过的其他主机中提取历史记录并将该历史记录附加到我的 master_history.txt 文件中。

能够快速搜索并找到您使用的棘手的正则表达式或您在 7 个月前提出的奇怪的 perl one-liner 总是很好的。


jma*_*g2k 7

我可以为最后一个提供修复:确保 env 变量 HISTCONTROL 没有指定“ignorespace”(或“ignoreboth”)。

但是我对多个并发会话感到痛苦。它在 bash 中处理得不好。


小智 7

我选择将历史记录放在一个 file-per-tty 中,因为多个人可以在同一台服务器上工作 - 将每个会话的命令分开,以便于审核。

# 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
Run Code Online (Sandbox Code Playgroud)

历史现在看起来像:

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
Run Code Online (Sandbox Code Playgroud)

文件看起来像:

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
Run Code Online (Sandbox Code Playgroud)


rou*_*ble 7

这是我对@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
Run Code Online (Sandbox Code Playgroud)


小智 6

这里我要指出一个问题

export PROMPT_COMMAND="${PROMPT_COMMAND:+$PROMPT_COMMAND$'\n'}history -a; history -c; history -r"
Run Code Online (Sandbox Code Playgroud)

PROMPT_COMMAND="$PROMPT_COMMAND;history -a; history -n"
Run Code Online (Sandbox Code Playgroud)

如果你运行 source ~/.bashrc,$PROMPT_COMMAND 会像

"history -a; history -c; history -r history -a; history -c; history -r"
Run Code Online (Sandbox Code Playgroud)

"history -a; history -n history -a; history -n"
Run Code Online (Sandbox Code Playgroud)

每次运行“source ~/.bashrc”时都会发生这种重复。您可以在每次运行 'source ~/.bashrc' 后通过运行 'echo $PROMPT_COMMAND' 来检查 PROMPT_COMMAND。

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

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

shopt -s histappend
PROMPT_COMMAND="history -a; history -c; history -r"
Run Code Online (Sandbox Code Playgroud)

它具有大部分功能,而没有上述问题。

另一点要说明的是:真的没有什么神奇的。PROMPT_COMMAND 只是一个普通的 bash 环境变量。在您获得 bash 提示($ 符号)之前,其中的命令会被执行。例如,您的 PROMPT_COMMAND 是“echo 123”,并且您在终端中运行“ls”。效果就像运行“ls; echo 123”。

$ PROMPT_COMMAND="echo 123"
Run Code Online (Sandbox Code Playgroud)

输出(就像运行 'PROMPT_COMMAND="echo 123"; $PROMPT_COMMAND'):

123
Run Code Online (Sandbox Code Playgroud)

运行以下命令:

$ echo 3
Run Code Online (Sandbox Code Playgroud)

输出:

3
123
Run Code Online (Sandbox Code Playgroud)

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

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

"history -r" 用于从 ~/.bash_history 读取历史命令到内存

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

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