sed: fix handling of 1d;1,3p and 1d;1,REGEXp

function                                             old     new   delta
process_files                                       2084    2173     +89

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
This commit is contained in:
Denys Vlasenko 2009-06-30 19:19:37 +02:00
parent f1fab09242
commit 8bca3e20b9
2 changed files with 265 additions and 234 deletions

View File

@ -865,63 +865,80 @@ static void process_files(void)
/* Prime the pump */ /* Prime the pump */
next_line = get_next_line(&next_gets_char); next_line = get_next_line(&next_gets_char);
/* go through every line in each file */ /* Go through every line in each file */
again: again:
substituted = 0; substituted = 0;
/* Advance to next line. Stop if out of lines. */ /* Advance to next line. Stop if out of lines. */
pattern_space = next_line; pattern_space = next_line;
if (!pattern_space) return; if (!pattern_space)
return;
last_gets_char = next_gets_char; last_gets_char = next_gets_char;
/* Read one line in advance so we can act on the last line, /* Read one line in advance so we can act on the last line,
* the '$' address */ * the '$' address */
next_line = get_next_line(&next_gets_char); next_line = get_next_line(&next_gets_char);
linenum++; linenum++;
/* For every line, go through all the commands */
restart: restart:
/* for every line, go through all the commands */
for (sed_cmd = G.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) { for (sed_cmd = G.sed_cmd_head.next; sed_cmd; sed_cmd = sed_cmd->next) {
int old_matched, matched; int old_matched, matched;
old_matched = sed_cmd->in_match; old_matched = sed_cmd->in_match;
/* Determine if this command matches this line: */ /* Determine if this command matches this line: */
/* Are we continuing a previous multi-line match? */
/* Are we continuing a previous multi-line match? */
sed_cmd->in_match = sed_cmd->in_match sed_cmd->in_match = sed_cmd->in_match
/* Or is no range necessary? */ /* Or is no range necessary? */
|| (!sed_cmd->beg_line && !sed_cmd->end_line || (!sed_cmd->beg_line && !sed_cmd->end_line
&& !sed_cmd->beg_match && !sed_cmd->end_match) && !sed_cmd->beg_match && !sed_cmd->end_match)
/* Or did we match the start of a numerical range? */ /* Or did we match the start of a numerical range? */
|| (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum)) || (sed_cmd->beg_line > 0 && (sed_cmd->beg_line == linenum
/* "shadowed beginning" case: "1d;1,ENDp" - p still matches at line 2
* even though 1d skipped line 1 which is a start line for p */
|| (sed_cmd->end_line && sed_cmd->beg_line < linenum && sed_cmd->end_line >= linenum)
|| (sed_cmd->end_match && sed_cmd->beg_line < linenum)
)
)
/* Or does this line match our begin address regex? */ /* Or does this line match our begin address regex? */
|| (beg_match(sed_cmd, pattern_space)) || (beg_match(sed_cmd, pattern_space))
/* Or did we match last line of input? */ /* Or did we match last line of input? */
|| (sed_cmd->beg_line == -1 && next_line == NULL); || (sed_cmd->beg_line == -1 && next_line == NULL);
/* Snapshot the value */
matched = sed_cmd->in_match; matched = sed_cmd->in_match;
/* Is this line the end of the current match? */ //bb_error_msg("cmd:'%c' matched:%d beg_line:%d end_line:%d linenum:%d",
//sed_cmd->cmd, matched, sed_cmd->beg_line, sed_cmd->end_line, linenum);
/* Is this line the end of the current match? */
if (matched) { if (matched) {
sed_cmd->in_match = !( int n = (
/* has the ending line come, or is this a single address command? */ /* has the ending line come, or is this a single address command? */
(sed_cmd->end_line ? sed_cmd->end_line ?
sed_cmd->end_line == -1 ? sed_cmd->end_line == -1 ?
!next_line !next_line
: (sed_cmd->end_line <= linenum) : (sed_cmd->end_line <= linenum)
: !sed_cmd->end_match : !sed_cmd->end_match
) );
if (!n) {
/* or does this line matches our last address regex */ /* or does this line matches our last address regex */
|| (sed_cmd->end_match && old_matched n = (sed_cmd->end_match
&& old_matched
&& (regexec(sed_cmd->end_match, && (regexec(sed_cmd->end_match,
pattern_space, 0, NULL, 0) == 0)) pattern_space, 0, NULL, 0) == 0)
); );
if (n && sed_cmd->beg_line > 0) {
/* Once matched, "n,regex" range is dead, disabling it */
regfree(sed_cmd->end_match);
free(sed_cmd->end_match);
sed_cmd->end_match = NULL;
}
}
sed_cmd->in_match = !n;
} }
/* Skip blocks of commands we didn't match. */ /* Skip blocks of commands we didn't match */
if (sed_cmd->cmd == '{') { if (sed_cmd->cmd == '{') {
if (sed_cmd->invert ? matched : !matched) { if (sed_cmd->invert ? matched : !matched) {
while (sed_cmd->cmd != '}') { while (sed_cmd->cmd != '}') {
@ -934,253 +951,254 @@ static void process_files(void)
} }
/* Okay, so did this line match? */ /* Okay, so did this line match? */
if (sed_cmd->invert ? !matched : matched) { if (sed_cmd->invert ? matched : !matched)
/* Update last used regex in case a blank substitute BRE is found */ continue; /* no */
if (sed_cmd->beg_match) {
G.previous_regex_ptr = sed_cmd->beg_match;
}
/* actual sedding */ /* Update last used regex in case a blank substitute BRE is found */
switch (sed_cmd->cmd) { if (sed_cmd->beg_match) {
G.previous_regex_ptr = sed_cmd->beg_match;
}
/* Print line number */ /* actual sedding */
case '=': switch (sed_cmd->cmd) {
fprintf(G.nonstdout, "%d\n", linenum);
break;
/* Write the current pattern space up to the first newline */ /* Print line number */
case 'P': case '=':
{ fprintf(G.nonstdout, "%d\n", linenum);
char *tmp = strchr(pattern_space, '\n'); break;
if (tmp) { /* Write the current pattern space up to the first newline */
*tmp = '\0'; case 'P':
/* TODO: explain why '\n' below */ {
sed_puts(pattern_space, '\n'); char *tmp = strchr(pattern_space, '\n');
*tmp = '\n';
break;
}
/* Fall Through */
}
/* Write the current pattern space to output */ if (tmp) {
case 'p': *tmp = '\0';
/* NB: we print this _before_ the last line /* TODO: explain why '\n' below */
* (of current file) is printed. Even if
* that line is nonterminated, we print
* '\n' here (gnu sed does the same) */
sed_puts(pattern_space, '\n'); sed_puts(pattern_space, '\n');
break; *tmp = '\n';
/* Delete up through first newline */
case 'D':
{
char *tmp = strchr(pattern_space, '\n');
if (tmp) {
tmp = xstrdup(tmp+1);
free(pattern_space);
pattern_space = tmp;
goto restart;
}
}
/* discard this line. */
case 'd':
goto discard_line;
/* Substitute with regex */
case 's':
if (!do_subst_command(sed_cmd, &pattern_space))
break;
substituted |= 1;
/* handle p option */
if (sed_cmd->sub_p)
sed_puts(pattern_space, last_gets_char);
/* handle w option */
if (sed_cmd->sw_file)
puts_maybe_newline(
pattern_space, sed_cmd->sw_file,
&sed_cmd->sw_last_char, last_gets_char);
break;
/* Append line to linked list to be printed later */
case 'a':
append(sed_cmd->string);
break;
/* Insert text before this line */
case 'i':
sed_puts(sed_cmd->string, '\n');
break;
/* Cut and paste text (replace) */
case 'c':
/* Only triggers on last line of a matching range. */
if (!sed_cmd->in_match)
sed_puts(sed_cmd->string, NO_EOL_CHAR);
goto discard_line;
/* Read file, append contents to output */
case 'r':
{
FILE *rfile;
rfile = fopen_for_read(sed_cmd->string);
if (rfile) {
char *line;
while ((line = xmalloc_fgetline(rfile))
!= NULL)
append(line);
xprint_and_close_file(rfile);
}
break; break;
} }
/* Fall Through */
}
/* Write pattern space to file. */ /* Write the current pattern space to output */
case 'w': case 'p':
/* NB: we print this _before_ the last line
* (of current file) is printed. Even if
* that line is nonterminated, we print
* '\n' here (gnu sed does the same) */
sed_puts(pattern_space, '\n');
break;
/* Delete up through first newline */
case 'D':
{
char *tmp = strchr(pattern_space, '\n');
if (tmp) {
tmp = xstrdup(tmp+1);
free(pattern_space);
pattern_space = tmp;
goto restart;
}
}
/* discard this line. */
case 'd':
goto discard_line;
/* Substitute with regex */
case 's':
if (!do_subst_command(sed_cmd, &pattern_space))
break;
substituted |= 1;
/* handle p option */
if (sed_cmd->sub_p)
sed_puts(pattern_space, last_gets_char);
/* handle w option */
if (sed_cmd->sw_file)
puts_maybe_newline( puts_maybe_newline(
pattern_space, sed_cmd->sw_file, pattern_space, sed_cmd->sw_file,
&sed_cmd->sw_last_char, last_gets_char); &sed_cmd->sw_last_char, last_gets_char);
break; break;
/* Read next line from input */ /* Append line to linked list to be printed later */
case 'n': case 'a':
if (!G.be_quiet) append(sed_cmd->string);
sed_puts(pattern_space, last_gets_char); break;
if (next_line) {
free(pattern_space);
pattern_space = next_line;
last_gets_char = next_gets_char;
next_line = get_next_line(&next_gets_char);
substituted = 0;
linenum++;
break;
}
/* fall through */
/* Quit. End of script, end of input. */ /* Insert text before this line */
case 'q': case 'i':
/* Exit the outer while loop */ sed_puts(sed_cmd->string, '\n');
free(next_line); break;
next_line = NULL;
goto discard_commands;
/* Append the next line to the current line */ /* Cut and paste text (replace) */
case 'N': case 'c':
{ /* Only triggers on last line of a matching range. */
int len; if (!sed_cmd->in_match)
/* If no next line, jump to end of script and exit. */ sed_puts(sed_cmd->string, NO_EOL_CHAR);
if (next_line == NULL) { goto discard_line;
/* Jump to end of script and exit */
free(next_line); /* Read file, append contents to output */
next_line = NULL; case 'r':
goto discard_line; {
/* append next_line, read new next_line. */ FILE *rfile;
}
len = strlen(pattern_space); rfile = fopen_for_read(sed_cmd->string);
pattern_space = realloc(pattern_space, len + strlen(next_line) + 2); if (rfile) {
pattern_space[len] = '\n'; char *line;
strcpy(pattern_space + len+1, next_line);
while ((line = xmalloc_fgetline(rfile))
!= NULL)
append(line);
xprint_and_close_file(rfile);
}
break;
}
/* Write pattern space to file. */
case 'w':
puts_maybe_newline(
pattern_space, sed_cmd->sw_file,
&sed_cmd->sw_last_char, last_gets_char);
break;
/* Read next line from input */
case 'n':
if (!G.be_quiet)
sed_puts(pattern_space, last_gets_char);
if (next_line) {
free(pattern_space);
pattern_space = next_line;
last_gets_char = next_gets_char; last_gets_char = next_gets_char;
next_line = get_next_line(&next_gets_char); next_line = get_next_line(&next_gets_char);
substituted = 0;
linenum++; linenum++;
break; break;
} }
/* fall through */
/* Test/branch if substitution occurred */ /* Quit. End of script, end of input. */
case 't': case 'q':
if (!substituted) break; /* Exit the outer while loop */
substituted = 0; free(next_line);
/* Fall through */ next_line = NULL;
/* Test/branch if substitution didn't occur */ goto discard_commands;
case 'T':
if (substituted) break;
/* Fall through */
/* Branch to label */
case 'b':
if (!sed_cmd->string) goto discard_commands;
else sed_cmd = branch_to(sed_cmd->string);
break;
/* Transliterate characters */
case 'y':
{
int i, j;
for (i = 0; pattern_space[i]; i++) { /* Append the next line to the current line */
for (j = 0; sed_cmd->string[j]; j += 2) { case 'N':
if (pattern_space[i] == sed_cmd->string[j]) { {
pattern_space[i] = sed_cmd->string[j + 1]; int len;
break; /* If no next line, jump to end of script and exit. */
} if (next_line == NULL) {
/* Jump to end of script and exit */
free(next_line);
next_line = NULL;
goto discard_line;
/* append next_line, read new next_line. */
}
len = strlen(pattern_space);
pattern_space = realloc(pattern_space, len + strlen(next_line) + 2);
pattern_space[len] = '\n';
strcpy(pattern_space + len+1, next_line);
last_gets_char = next_gets_char;
next_line = get_next_line(&next_gets_char);
linenum++;
break;
}
/* Test/branch if substitution occurred */
case 't':
if (!substituted) break;
substituted = 0;
/* Fall through */
/* Test/branch if substitution didn't occur */
case 'T':
if (substituted) break;
/* Fall through */
/* Branch to label */
case 'b':
if (!sed_cmd->string) goto discard_commands;
else sed_cmd = branch_to(sed_cmd->string);
break;
/* Transliterate characters */
case 'y':
{
int i, j;
for (i = 0; pattern_space[i]; i++) {
for (j = 0; sed_cmd->string[j]; j += 2) {
if (pattern_space[i] == sed_cmd->string[j]) {
pattern_space[i] = sed_cmd->string[j + 1];
break;
} }
} }
break;
} }
case 'g': /* Replace pattern space with hold space */
free(pattern_space);
pattern_space = xstrdup(G.hold_space ? G.hold_space : "");
break;
case 'G': /* Append newline and hold space to pattern space */
{
int pattern_space_size = 2;
int hold_space_size = 0;
if (pattern_space) break;
pattern_space_size += strlen(pattern_space);
if (G.hold_space)
hold_space_size = strlen(G.hold_space);
pattern_space = xrealloc(pattern_space,
pattern_space_size + hold_space_size);
if (pattern_space_size == 2)
pattern_space[0] = 0;
strcat(pattern_space, "\n");
if (G.hold_space)
strcat(pattern_space, G.hold_space);
last_gets_char = '\n';
break;
}
case 'h': /* Replace hold space with pattern space */
free(G.hold_space);
G.hold_space = xstrdup(pattern_space);
break;
case 'H': /* Append newline and pattern space to hold space */
{
int hold_space_size = 2;
int pattern_space_size = 0;
if (G.hold_space)
hold_space_size += strlen(G.hold_space);
if (pattern_space)
pattern_space_size = strlen(pattern_space);
G.hold_space = xrealloc(G.hold_space,
hold_space_size + pattern_space_size);
if (hold_space_size == 2)
*G.hold_space = 0;
strcat(G.hold_space, "\n");
if (pattern_space)
strcat(G.hold_space, pattern_space);
break;
}
case 'x': /* Exchange hold and pattern space */
{
char *tmp = pattern_space;
pattern_space = G.hold_space ? : xzalloc(1);
last_gets_char = '\n';
G.hold_space = tmp;
break;
}
}
} }
} case 'g': /* Replace pattern space with hold space */
free(pattern_space);
pattern_space = xstrdup(G.hold_space ? G.hold_space : "");
break;
case 'G': /* Append newline and hold space to pattern space */
{
int pattern_space_size = 2;
int hold_space_size = 0;
if (pattern_space)
pattern_space_size += strlen(pattern_space);
if (G.hold_space)
hold_space_size = strlen(G.hold_space);
pattern_space = xrealloc(pattern_space,
pattern_space_size + hold_space_size);
if (pattern_space_size == 2)
pattern_space[0] = 0;
strcat(pattern_space, "\n");
if (G.hold_space)
strcat(pattern_space, G.hold_space);
last_gets_char = '\n';
break;
}
case 'h': /* Replace hold space with pattern space */
free(G.hold_space);
G.hold_space = xstrdup(pattern_space);
break;
case 'H': /* Append newline and pattern space to hold space */
{
int hold_space_size = 2;
int pattern_space_size = 0;
if (G.hold_space)
hold_space_size += strlen(G.hold_space);
if (pattern_space)
pattern_space_size = strlen(pattern_space);
G.hold_space = xrealloc(G.hold_space,
hold_space_size + pattern_space_size);
if (hold_space_size == 2)
*G.hold_space = 0;
strcat(G.hold_space, "\n");
if (pattern_space)
strcat(G.hold_space, pattern_space);
break;
}
case 'x': /* Exchange hold and pattern space */
{
char *tmp = pattern_space;
pattern_space = G.hold_space ? : xzalloc(1);
last_gets_char = '\n';
G.hold_space = tmp;
break;
}
} /* switch */
} /* for each cmd */
/* /*
* exit point from sedding... * Exit point from sedding...
*/ */
discard_commands: discard_commands:
/* we will print the line unless we were told to be quiet ('-n') /* we will print the line unless we were told to be quiet ('-n')

View File

@ -207,4 +207,17 @@ testing "sed n command must reset 'substituted' bit" \
"sed 's/1/x/;T;n;: next;s/3/y/;t quit;n;b next;: quit;q'" \ "sed 's/1/x/;T;n;: next;s/3/y/;t quit;n;b next;: quit;q'" \
"0\nx\n2\ny\n" "" "0\n1\n2\n3\n" "0\nx\n2\ny\n" "" "0\n1\n2\n3\n"
testing "sed d does not break n,m matching" \
"sed -n '1d;1,3p'" \
"second\nthird\n" "" "first\nsecond\nthird\nfourth\n"
testing "sed d does not break n,regex matching" \
"sed -n '1d;1,/hir/p'" \
"second\nthird\n" "" "first\nsecond\nthird\nfourth\n"
testing "sed d does not break n,regex matching #2" \
"sed -n '1,5d;1,/hir/p'" \
"second2\nthird2\n" "" \
"first\nsecond\nthird\nfourth\n""first2\nsecond2\nthird2\nfourth2\n"
exit $FAILCOUNT exit $FAILCOUNT