这个问题已经困扰我三年了。我不知道如何完整地描述这个问题,但我想我终于可以描述一种重现它的方法。您的情况可能会有所不同。我有各种版本的 ubuntu 服务器和台式机,以及几台处于不同失修状态的 gentoo 机器。它们似乎都有自己的特点,尽管有相似之处。
尝试一下,如果发现同样的事情请告诉我。
- 弹出两个 xterm (TERM=xterm)
- 调整其中一个的大小,使它们不一样
- 一次发出 screen -R test1 (TERM=screen)
- 另一个是 screen -x test1
- 哇哦,在一个中输入会在另一个中显示;尽管注意到它们不同的大小会产生伪影和东西
- 在 shell 中发出几个命令
- 对不太合适的那个点击^AF,现在它就合适了!!
- 稍微回顾一下历史记录
- 转到 6
最终,您会注意到几条历史记录行合并在一起。如果您没有注意到,那么这是我的设置所特有的,它跨越了各种发行版和计算机;所以这对我来说是一个令人困惑的概念。
如果你看到了我所看到的东西那么就是这样:
bash$ ls -al
bash$ ps auxfw
变成这样:
bash$ ls -al; ps auxfw
这种情况并非每次都会发生。我必须认真考虑一下——除非我不想发生这种情况,否则它总是会发生。在某些系统(或组合)上,我会得到像上面示例一样的行分隔符。在某些系统上,我没有。我在某些系统上得到行分隔符似乎表明 bash 支持这种行为。它的历史记录完全由 libreadline 处理,在仔细阅读(即仔细阅读)手册页后,我找不到用于组合两个历史记录行的单个 readline 设置。我在 bash 手册页中也找不到任何东西。
那么,我该如何故意调用它?或者,如果我做不到,我该如何完全禁用它?我会选择任何一个答案作为解决方案。目前,我只有在不需要它时才会看到它。
答案1
来自 bash-4.2-rc2 的 CHANGELOG:
本文档详细介绍了此版本 bash-4.2-alpha 与上一版本 bash-4.1-release 之间的变化。
q. 修复了在某些情况下导致伪分号被添加到命令历史记录中的错误。
候选版本的 tarball 现已可用这里。
编辑:
以下是 Bash 3.2 和 Bash 4.2 RC2 之间的一些部分差异:
y.tab.c:
#if defined (HISTORY) #if defined (HISTORY)
/* A list of tokens which can be followed by newlines, but not by /* A list of tokens which can be followed by newlines, but not by
semi-colons. When concatenating multiple lines of history, the semi-colons. When concatenating multiple lines of history, the
newline separator for such tokens is replaced with a space. */ newline separator for such tokens is replaced with a space. */
static int no_semi_successors[] = { | static const int no_semi_successors[] = {
'\n', '{', '(', ')', ';', '&', '|', '\n', '{', '(', ')', ';', '&', '|',
CASE, DO, ELSE, IF, SEMI_SEMI, THEN, UNTIL, WHILE, AND_AND, OR_OR, IN, | CASE, DO, ELSE, IF, SEMI_SEMI, SEMI_AND, SEMI_SEMI_AND, THEN, UNTIL,
> WHILE, AND_AND, OR_OR, IN,
0 0
}; };
/* If we are not within a delimited expression, try to be smart /* If we are not within a delimited expression, try to be smart
about which separators can be semi-colons and which must be about which separators can be semi-colons and which must be
newlines. Returns the string that should be added into the newlines. Returns the string that should be added into the
history entry. */ | history entry. LINE is the line we're about to add; it helps
> make some more intelligent decisions in certain cases. */
char * char *
history_delimiting_chars () | history_delimiting_chars (line)
> const char *line;
{ {
> static int last_was_heredoc = 0; /* was the last entry the start of a here document? */
register int i; register int i;
> if ((parser_state & PST_HEREDOC) == 0)
> last_was_heredoc = 0;
>
if (dstack.delimiter_depth != 0) if (dstack.delimiter_depth != 0)
return ("\n"); return ("\n");
|
> /* We look for current_command_line_count == 2 because we are looking to
> add the first line of the body of the here document (the second line
> of the command). We also keep LAST_WAS_HEREDOC as a private sentinel
> variable to note when we think we added the first line of a here doc
> (the one with a "<<" somewhere in it) */
> if (parser_state & PST_HEREDOC)
> {
> if (last_was_heredoc)
> {
> last_was_heredoc = 0;
> return "\n";
> }
> return (current_command_line_count == 2 ? "\n" : "");
> }
>
/* First, handle some special cases. */ /* First, handle some special cases. */
/*(*/ /*(*/
/* If we just read `()', assume it's a function definition, and don't /* If we just read `()', assume it's a function definition, and don't
add a semicolon. If the token before the `)' was not `(', and we're add a semicolon. If the token before the `)' was not `(', and we're
not in the midst of parsing a case statement, assume it's a not in the midst of parsing a case statement, assume it's a
parenthesized command and add the semicolon. */ parenthesized command and add the semicolon. */
/*)(*/ /*)(*/
if (token_before_that == ')') if (token_before_that == ')')
{ {
if (two_tokens_ago == '(') /*)*/ /* function def */ if (two_tokens_ago == '(') /*)*/ /* function def */
return " "; return " ";
/* This does not work for subshells inside case statement /* This does not work for subshells inside case statement
command lists. It's a suboptimal solution. */ command lists. It's a suboptimal solution. */
else if (parser_state & PST_CASESTMT) /* case statement pattern */ else if (parser_state & PST_CASESTMT) /* case statement pattern */
return " "; return " ";
else else
return "; "; /* (...) subshell */ return "; "; /* (...) subshell */
} }
else if (token_before_that == WORD && two_tokens_ago == FUNCTION) else if (token_before_that == WORD && two_tokens_ago == FUNCTION)
return " "; /* function def using `function name' without `()' */ return " "; /* function def using `function name' without `()' */
> /* If we're not in a here document, but we think we're about to parse one,
> and we would otherwise return a `;', return a newline to delimit the
> line with the here-doc delimiter */
> else if ((parser_state & PST_HEREDOC) == 0 && current_command_line_count > 1 && last_read_token == '\n' && strstr (line, "<<"))
> {
> last_was_heredoc = 1;
> return "\n";
> }
>
else if (token_before_that == WORD && two_tokens_ago == FOR) else if (token_before_that == WORD && two_tokens_ago == FOR)
{ {
/* Tricky. `for i\nin ...' should not have a semicolon, but /* Tricky. `for i\nin ...' should not have a semicolon, but
`for i\ndo ...' should. We do what we can. */ `for i\ndo ...' should. We do what we can. */
for (i = shell_input_line_index; whitespace(shell_input_line[i]); i++) | for (i = shell_input_line_index; whitespace (shell_input_line[i]); i++)
; ;
if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n') if (shell_input_line[i] && shell_input_line[i] == 'i' && shell_input_line[i+1] == 'n')
return " "; return " ";
return ";"; return ";";
} }
else if (two_tokens_ago == CASE && token_before_that == WORD && (parser_state & PST_CASESTMT)) else if (two_tokens_ago == CASE && token_before_that == WORD && (parser_state & PST_CASESTMT))
return " "; return " ";
for (i = 0; no_semi_successors[i]; i++) for (i = 0; no_semi_successors[i]; i++)
{ {
if (token_before_that == no_semi_successors[i]) if (token_before_that == no_semi_successors[i])
return (" "); return (" ");
} }
return ("; "); return ("; ");
} }
#endif /* HISTORY */ #endif /* HISTORY */
.bashhist.c:
/* Add a line to the history list. /* Add a line to the history list.
The variable COMMAND_ORIENTED_HISTORY controls the style of history The variable COMMAND_ORIENTED_HISTORY controls the style of history
remembering; when non-zero, and LINE is not the first line of a remembering; when non-zero, and LINE is not the first line of a
complete parser construct, append LINE to the last history line instead complete parser construct, append LINE to the last history line instead
of adding it as a new line. */ of adding it as a new line. */
void void
bash_add_history (line) bash_add_history (line)
char *line; char *line;
{ {
int add_it, offset, curlen; int add_it, offset, curlen;
HIST_ENTRY *current, *old; HIST_ENTRY *current, *old;
char *chars_to_add, *new_line; char *chars_to_add, *new_line;
add_it = 1; add_it = 1;
if (command_oriented_history && current_command_line_count > 1) if (command_oriented_history && current_command_line_count > 1)
{ {
chars_to_add = literal_history ? "\n" : history_delimiting_chars (); | chars_to_add = literal_history ? "\n" : history_delimiting_chars (line);
using_history (); using_history ();
current = previous_history (); current = previous_history ();
if (current) if (current)
{ {
/* If the previous line ended with an escaped newline (escaped /* If the previous line ended with an escaped newline (escaped
with backslash, but otherwise unquoted), then remove the quoted with backslash, but otherwise unquoted), then remove the quoted
newline, since that is what happens when the line is parsed. */ newline, since that is what happens when the line is parsed. */
curlen = strlen (current->line); curlen = strlen (current->line);
if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\\' && if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\\' &&
current->line[curlen - 2] != '\\') current->line[curlen - 2] != '\\')
{ {
current->line[curlen - 1] = '\0'; current->line[curlen - 1] = '\0';
curlen--; curlen--;
chars_to_add = ""; chars_to_add = "";
} }
> /* If we're not in some kind of quoted construct, the current history
> entry ends with a newline, and we're going to add a semicolon,
> don't. In some cases, it results in a syntax error (e.g., before
> a close brace), and it should not be needed. */
> if (dstack.delimiter_depth == 0 && current->line[curlen - 1] == '\n' && *chars_to_add == ';')
> chars_to_add++;
>
new_line = (char *)xmalloc (1 new_line = (char *)xmalloc (1
+ curlen + curlen
+ strlen (line) + strlen (line)
+ strlen (chars_to_add)); + strlen (chars_to_add));
sprintf (new_line, "%s%s%s", current->line, chars_to_add, line); sprintf (new_line, "%s%s%s", current->line, chars_to_add, line);
offset = where_history (); offset = where_history ();
old = replace_history_entry (offset, new_line, current->data); old = replace_history_entry (offset, new_line, current->data);
free (new_line); free (new_line);
if (old) if (old)
free_history_entry (old); free_history_entry (old);
add_it = 0; add_it = 0;
} }
} }
if (add_it) if (add_it)
really_add_history (line); really_add_history (line);
> #if defined (SYSLOG_HISTORY)
> bash_syslog_history (line);
> #endif
>
using_history (); using_history ();
} }