Do CHANGELOG para o bash-4.2-rc2:
This document details the changes between this version, bash-4.2-alpha, and the previous version, bash-4.1-release.
q. Fixed a bug that caused spurious semicolons to be added into the command history in certain cases.
O tarball do release candidate está disponível aqui .
Editar:
Aqui estão alguns diffs parciais entre o Bash 3.2 e o 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] = '#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 */
'; current->line[curlen - 1] = '/* 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] = '%pre%'; current->line[curlen - 1] = '%pre%';
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 ();
} }
';
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 ();
} }