一、为什么有这个问题
在一台服务器上,可能存在多个用户共用一个账号的问题;或者使用同一个用户打开多个终端,这些终端就构成了很多的会话(session)。当然这些会话在使用的过程中必然不会相互干扰,问题在于它们生成的历史记录该如何相互交互?
下面分析结合bash4.0源码分析。
二、history的存储位置
通过bash的代码可以看到,bash文件使用的是环境变量HISTFILE来控制写入文件的。通过bash的man手册可以看到
HISTFILE
The name of the file in which command history is saved (see HISTORY below). The default value is ~/.bash_history. If unset, the command history is not saved when an inter‐
active shell exits.
也就是说,默认是保存在"~/.bash_history"文件中。
三、history文件的行为
同样是在bash的man手册中,可以看到关于history的说明。
启动时,HISTFILE的内容会被读取(On startup, the history is initialized from the file named by the variable HISTFILE (default ~/.bash_history))
(交互式shell)退出时,最IN的%HISTSIZE行会被拷贝到HISTFILE文件中。如果histappend选项使能的话,这些行会追加到历史文件中,否则history文件会被重写( When an interactive shell exits, the last $HISTSIZE lines are copied from the history list to $HISTFILE. If the histappend shell option is enabled (see the description of shopt
under SHELL BUILTIN COMMANDS below), the lines are appended to the history file, otherwise the history file is overwritten. If HISTFILE is unset, or if the history file is
unwritable, the history is not saved. )
HISTORY
When the -o history option to the set builtin is enabled, the shell provides access to the command history, the list of commands previously typed. The value of the HISTSIZE vari‐
able is used as the number of commands to save in a history list. The text of the last HISTSIZE commands (default 500) is saved. The shell stores each command in the history list
prior to parameter and variable expansion (see EXPANSION above) but after history expansion is performed, subject to the values of the shell variables HISTIGNORE and HISTCONTROL.
On startup, the history is initialized from the file named by the variable HISTFILE (default ~/.bash_history). The file named by the value of HISTFILE is truncated, if necessary,
to contain no more than the number of lines specified by the value of HISTFILESIZE. When the history file is read, lines beginning with the history comment character followed imme‐
diately by a digit are interpreted as timestamps for the preceding history line. These timestamps are optionally displayed depending on the value of the HISTTIMEFORMAT variable.
When an interactive shell exits, the last $HISTSIZE lines are copied from the history list to $HISTFILE. If the histappend shell option is enabled (see the description of shopt
under SHELL BUILTIN COMMANDS below), the lines are appended to the history file, otherwise the history file is overwritten. If HISTFILE is unset, or if the history file is
unwritable, the history is not saved. If the HISTTIMEFORMAT variable is set, time stamps are written to the history file, marked with the history comment character, so they may be
preserved across shell sessions. This uses the history comment character to distinguish timestamps from other history lines. After saving the history, the history file is trun‐
cated to contain no more than HISTFILESIZE lines. If HISTFILESIZE is not set, no truncation is performed.
The builtin command fc (see SHELL BUILTIN COMMANDS below) may be used to list or edit and re-execute a portion of the history list. The history builtin may be used to display or
modify the history list and manipulate the history file. When using command-line editing, search commands are available in each editing mode that provide access to the history
list.
The shell allows control over which commands are saved on the history list. The HISTCONTROL and HISTIGNORE variables may be set to cause the shell to save only a subset of the com‐
mands entered. The cmdhist shell option, if enabled, causes the shell to attempt to save each line of a multi-line command in the same history entry, adding semicolons where neces‐
sary to preserve syntactic correctness. The lithist shell option causes the shell to save the command with embedded newlines instead of semicolons. See the description of the
shopt builtin below under SHELL BUILTIN COMMANDS for information on setting and unsetting shell options.
四、bash4.0的代码看
可以看到,是追加还是重写是通过下面的逻辑判断实现的
history_lines_this_session <= where_history () || force_append_history
其中的逻辑判断是histappend选项,history_lines_this_session是这个session生成的(新输入的)命令集合,而where_history ()是整个history文件的大小。通常情况下(如果没有设置实时读取history等骚操作)下,这个条件也是满足的,所以即使没有设置这个histappend,常规行为也是会追加而不是覆盖。
bash-4.0\bashhist.c
/* If this is an interactive shell, then append the lines executed
this session to the history file. */
int
maybe_save_shell_history ()
{
int result;
char *hf;
result = 0;
if (history_lines_this_session)
{
hf = get_string_value ("HISTFILE");
if (hf && *hf)
{
/* If the file doesn't exist, then create it. */
if (file_exists (hf) == 0)
{
int file;
file = open (hf, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (file != -1)
close (file);
}
/* Now actually append the lines if the history hasn't been
stifled. If the history has been stifled, rewrite the
history file. */
using_history ();
if (history_lines_this_session <= where_history () || force_append_history)
{
result = append_history (history_lines_this_session, hf);
history_lines_in_file += history_lines_this_session;
}
else
{
result = write_history (hf);
history_lines_in_file = history_lines_this_session;
}
history_lines_this_session = 0;
sv_histsize ("HISTFILESIZE");
}
}
return (result);
}
五、测试
1、第一个终端执行命令
tsecer@harry.term1: shopt -u histappend
tsecer@harry.term1: shopt histappend
histappend off
tsecer@harry.term1: echo after histappend off in term1
after histappend off in term1
tsecer@harry.term1:
2、第二个终端执行命令
tsecer@harry.term2: shopt -u histappend
tsecer@harry.term2: shopt histappend
histappend off
tsecer@harry.term2: echo histappend after term2
histappend after term2
tsecer@harry.term2:
3、分别exit两个终端,并在第三个终端中查看历史记录
可以同时看到两个终端的输出。
shopt -u histappend
shopt histappend
echo after histappend off in term1
exit
shopt -u histappend
shopt histappend
echo histappend after term2
exit
六、多环境下配置建议
1、强制追加模式
之前只是说常规情况下历史记录都是追加的,但是还是开启histappen的容错性更好。
2、命令时间戳
遗憾的是bash并不支持记录命令在哪个终端键入的,所以最好在基类命令的执行时间,从而在合并之后可以追溯到历史时间。
HISTTIMEFORMAT
If this variable is set and not null, its value is used as a format string for strftime(3) to print the time stamp associated with each history entry displayed by the history
builtin. If this variable is set, time stamps are written to the history file so they may be preserved across shell sessions. This uses the history comment character to
distinguish timestamps from other history lines.