mirror of https://github.com/mirror/busybox.git
sed: fix 2 bugs (one testsuite entry + one newly found)
but more importantly make code more understandable1_5_stable
parent
826c85f382
commit
fe7a9f1277
118
editors/sed.c
118
editors/sed.c
|
@ -72,7 +72,7 @@ typedef struct sed_cmd_s {
|
||||||
int beg_line; /* 'sed 1p' 0 == apply commands to all lines */
|
int beg_line; /* 'sed 1p' 0 == apply commands to all lines */
|
||||||
int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */
|
int end_line; /* 'sed 1,3p' 0 == one line only. -1 = last line ($) */
|
||||||
|
|
||||||
FILE *file; /* File (sw) command writes to, -1 for none. */
|
FILE *sw_file; /* File (sw) command writes to, -1 for none. */
|
||||||
char *string; /* Data string for (saicytb) commands. */
|
char *string; /* Data string for (saicytb) commands. */
|
||||||
|
|
||||||
unsigned short which_match; /* (s) Which match to replace (0 for all) */
|
unsigned short which_match; /* (s) Which match to replace (0 for all) */
|
||||||
|
@ -82,7 +82,7 @@ typedef struct sed_cmd_s {
|
||||||
unsigned int in_match:1; /* Next line also included in match? */
|
unsigned int in_match:1; /* Next line also included in match? */
|
||||||
unsigned int sub_p:1; /* (s) print option */
|
unsigned int sub_p:1; /* (s) print option */
|
||||||
|
|
||||||
// int sw_last_char; /* Last line written by (sw) had no '\n' */
|
char sw_last_char; /* Last line written by (sw) had no '\n' */
|
||||||
|
|
||||||
/* GENERAL FIELDS */
|
/* GENERAL FIELDS */
|
||||||
char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
|
char cmd; /* The command char: abcdDgGhHilnNpPqrstwxy:={} */
|
||||||
|
@ -130,8 +130,8 @@ static void sed_free_and_close_stuff(void)
|
||||||
while (sed_cmd) {
|
while (sed_cmd) {
|
||||||
sed_cmd_t *sed_cmd_next = sed_cmd->next;
|
sed_cmd_t *sed_cmd_next = sed_cmd->next;
|
||||||
|
|
||||||
if (sed_cmd->file)
|
if (sed_cmd->sw_file)
|
||||||
xprint_and_close_file(sed_cmd->file);
|
xprint_and_close_file(sed_cmd->sw_file);
|
||||||
|
|
||||||
if (sed_cmd->beg_match) {
|
if (sed_cmd->beg_match) {
|
||||||
regfree(sed_cmd->beg_match);
|
regfree(sed_cmd->beg_match);
|
||||||
|
@ -423,7 +423,7 @@ static char *parse_cmd_args(sed_cmd_t *sed_cmd, char *cmdstr)
|
||||||
bb_error_msg_and_die("command only uses one address");
|
bb_error_msg_and_die("command only uses one address");
|
||||||
cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string);
|
cmdstr += parse_file_cmd(sed_cmd, cmdstr, &sed_cmd->string);
|
||||||
if (sed_cmd->cmd == 'w')
|
if (sed_cmd->cmd == 'w')
|
||||||
sed_cmd->file = xfopen(sed_cmd->string, "w");
|
sed_cmd->sw_file = xfopen(sed_cmd->string, "w");
|
||||||
/* handle branch commands */
|
/* handle branch commands */
|
||||||
} else if (strchr(":btT", sed_cmd->cmd)) {
|
} else if (strchr(":btT", sed_cmd->cmd)) {
|
||||||
int length;
|
int length;
|
||||||
|
@ -717,21 +717,22 @@ static void add_input_file(FILE *file)
|
||||||
bbg.input_file_list[bbg.input_file_count++] = file;
|
bbg.input_file_list[bbg.input_file_count++] = file;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum {
|
|
||||||
MASK_NO_EOL_CHAR = 0x100,
|
|
||||||
//MASK_FIRST_LINE = 0x200,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Get next line of input from bbg.input_file_list, flushing append buffer and
|
/* Get next line of input from bbg.input_file_list, flushing append buffer and
|
||||||
* noting if we ran out of files without a newline on the last line we read.
|
* noting if we ran out of files without a newline on the last line we read.
|
||||||
*/
|
*/
|
||||||
static char *get_next_line(int *last_char)
|
enum {
|
||||||
|
NO_EOL_CHAR = 1,
|
||||||
|
};
|
||||||
|
static char *get_next_line(char *gets_char)
|
||||||
{
|
{
|
||||||
char *temp = NULL;
|
char *temp = NULL;
|
||||||
int len, lc;
|
int len, gc;
|
||||||
|
|
||||||
lc = 0;
|
|
||||||
flush_append();
|
flush_append();
|
||||||
|
|
||||||
|
/* will be returned if last line in the file
|
||||||
|
* doesn't end with either '\n' or '\0' */
|
||||||
|
gc = NO_EOL_CHAR;
|
||||||
while (bbg.current_input_file < bbg.input_file_count) {
|
while (bbg.current_input_file < bbg.input_file_count) {
|
||||||
/* Read line up to a newline or NUL byte, inclusive,
|
/* Read line up to a newline or NUL byte, inclusive,
|
||||||
* return malloc'ed char[]. length of the chunk read
|
* return malloc'ed char[]. length of the chunk read
|
||||||
|
@ -743,26 +744,29 @@ static char *get_next_line(int *last_char)
|
||||||
char c = temp[len-1];
|
char c = temp[len-1];
|
||||||
if (c == '\n' || c == '\0') {
|
if (c == '\n' || c == '\0') {
|
||||||
temp[len-1] = '\0';
|
temp[len-1] = '\0';
|
||||||
lc |= (unsigned char)c;
|
gc = (unsigned char)c;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* will be returned if last line in the file
|
/* NB: I had the idea of peeking next file(s) and returning
|
||||||
* doesn't end with either '\n' or '\0' */
|
* NO_EOL_CHAR only if it is the *last* non-empty
|
||||||
lc |= MASK_NO_EOL_CHAR;
|
* input file. But there is a case where this won't work:
|
||||||
|
* file1: "a woo\nb woo"
|
||||||
|
* file2: "c no\nd no"
|
||||||
|
* sed -ne 's/woo/bang/p' input1 input2 => "a bang\nb bang"
|
||||||
|
* (note: *no* newline after "b bang"!) */
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* Close this file and advance to next one */
|
/* Close this file and advance to next one */
|
||||||
fclose(bbg.input_file_list[bbg.current_input_file++]);
|
fclose(bbg.input_file_list[bbg.current_input_file++]);
|
||||||
/* "this is the first line from new input file" */
|
|
||||||
//lc |= MASK_FIRST_LINE;
|
|
||||||
}
|
}
|
||||||
*last_char = lc;
|
*gets_char = gc;
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Output line of text. */
|
/* Output line of text. */
|
||||||
/* Note:
|
/* Note:
|
||||||
* The tricks with MASK_FIRST_LINE and last_puts_char are there to emulate gnu sed.
|
* The tricks with NO_EOL_CHAR and last_puts_char are there to emulate gnu sed.
|
||||||
* Without them, we had this:
|
* Without them, we had this:
|
||||||
* echo -n thingy >z1
|
* echo -n thingy >z1
|
||||||
* echo -n again >z2
|
* echo -n again >z2
|
||||||
|
@ -774,37 +778,32 @@ static char *get_next_line(int *last_char)
|
||||||
* bbox:
|
* bbox:
|
||||||
* 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn|
|
* 00000000 74 68 7a 6e 67 79 61 67 61 7a 6e |thzngyagazn|
|
||||||
*/
|
*/
|
||||||
static void puts_maybe_newline(char *s, FILE *file, int last_char)
|
static void puts_maybe_newline(char *s, FILE *file, char *last_puts_char, char last_gets_char)
|
||||||
{
|
{
|
||||||
static char last_puts_char = '\n';
|
char lpc = *last_puts_char;
|
||||||
|
|
||||||
/* Is this a first line from new file
|
/* Is this a first line from new file
|
||||||
* and old file didn't end with '\n' or '\0'? */
|
* and old file didn't end with '\n' or '\0'? */
|
||||||
// if ((last_char & MASK_FIRST_LINE) && last_puts_char != '\n') {
|
if (lpc != '\n' && lpc != '\0') {
|
||||||
if (last_puts_char != '\n' && last_puts_char != '\0') {
|
|
||||||
fputc('\n', file);
|
fputc('\n', file);
|
||||||
last_puts_char = '\n';
|
lpc = '\n';
|
||||||
}
|
}
|
||||||
fputs(s, file);
|
fputs(s, file);
|
||||||
/* why 'x'? - just something which is not '\n' */
|
/* 'x' - just something which is not '\n', '\0' or NO_EOL_CHAR */
|
||||||
if (s[0])
|
if (s[0])
|
||||||
last_puts_char = 'x';
|
lpc = 'x';
|
||||||
if (!(last_char & MASK_NO_EOL_CHAR)) { /* had trailing '\n' or '\0'? */
|
if (last_gets_char != NO_EOL_CHAR) { /* had trailing '\n' or '\0'? */
|
||||||
last_char &= 0xff;
|
fputc(last_gets_char, file);
|
||||||
fputc(last_char, file);
|
lpc = last_gets_char;
|
||||||
last_puts_char = last_char;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ferror(file)) {
|
if (ferror(file)) {
|
||||||
xfunc_error_retval = 4; /* It's what gnu sed exits with... */
|
xfunc_error_retval = 4; /* It's what gnu sed exits with... */
|
||||||
bb_error_msg_and_die(bb_msg_write_error);
|
bb_error_msg_and_die(bb_msg_write_error);
|
||||||
}
|
}
|
||||||
|
*last_puts_char = lpc;
|
||||||
/* Seems to be unused */
|
|
||||||
/*return last_char;*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define sed_puts(s, n) (puts_maybe_newline(s, bbg.nonstdout, n))
|
#define sed_puts(s, n) (puts_maybe_newline(s, bbg.nonstdout, &last_puts_char, n))
|
||||||
|
|
||||||
/* Process all the lines in all the files */
|
/* Process all the lines in all the files */
|
||||||
|
|
||||||
|
@ -812,12 +811,13 @@ static void process_files(void)
|
||||||
{
|
{
|
||||||
char *pattern_space, *next_line;
|
char *pattern_space, *next_line;
|
||||||
int linenum = 0;
|
int linenum = 0;
|
||||||
int last_char, next_last_char = 0;
|
char last_puts_char = '\n';
|
||||||
|
char last_gets_char, next_gets_char;
|
||||||
sed_cmd_t *sed_cmd;
|
sed_cmd_t *sed_cmd;
|
||||||
int substituted;
|
int substituted;
|
||||||
|
|
||||||
/* Prime the pump */
|
/* Prime the pump */
|
||||||
next_line = get_next_line(&next_last_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:
|
||||||
|
@ -826,11 +826,11 @@ again:
|
||||||
/* 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_char = next_last_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_last_char);
|
next_line = get_next_line(&next_gets_char);
|
||||||
linenum++;
|
linenum++;
|
||||||
restart:
|
restart:
|
||||||
/* for every line, go through all the commands */
|
/* for every line, go through all the commands */
|
||||||
|
@ -924,7 +924,7 @@ restart:
|
||||||
* (of current file) is printed. Even if
|
* (of current file) is printed. Even if
|
||||||
* that line is nonterminated, we print
|
* that line is nonterminated, we print
|
||||||
* '\n' here (gnu sed does the same) */
|
* '\n' here (gnu sed does the same) */
|
||||||
sed_puts(pattern_space, (last_char & 0x200) | '\n');
|
sed_puts(pattern_space, '\n');
|
||||||
break;
|
break;
|
||||||
/* Delete up through first newline */
|
/* Delete up through first newline */
|
||||||
case 'D':
|
case 'D':
|
||||||
|
@ -950,12 +950,12 @@ restart:
|
||||||
|
|
||||||
/* handle p option */
|
/* handle p option */
|
||||||
if (sed_cmd->sub_p)
|
if (sed_cmd->sub_p)
|
||||||
sed_puts(pattern_space, last_char);
|
sed_puts(pattern_space, last_gets_char);
|
||||||
/* handle w option */
|
/* handle w option */
|
||||||
if (sed_cmd->file)
|
if (sed_cmd->sw_file)
|
||||||
/*sed_cmd->sw_last_char =*/ puts_maybe_newline(
|
puts_maybe_newline(
|
||||||
pattern_space, sed_cmd->file,
|
pattern_space, sed_cmd->sw_file,
|
||||||
last_char);
|
&sed_cmd->sw_last_char, last_gets_char);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Append line to linked list to be printed later */
|
/* Append line to linked list to be printed later */
|
||||||
|
@ -972,7 +972,7 @@ restart:
|
||||||
case 'c':
|
case 'c':
|
||||||
/* Only triggers on last line of a matching range. */
|
/* Only triggers on last line of a matching range. */
|
||||||
if (!sed_cmd->in_match)
|
if (!sed_cmd->in_match)
|
||||||
sed_puts(sed_cmd->string, MASK_NO_EOL_CHAR);
|
sed_puts(sed_cmd->string, NO_EOL_CHAR);
|
||||||
goto discard_line;
|
goto discard_line;
|
||||||
|
|
||||||
/* Read file, append contents to output */
|
/* Read file, append contents to output */
|
||||||
|
@ -995,20 +995,20 @@ restart:
|
||||||
|
|
||||||
/* Write pattern space to file. */
|
/* Write pattern space to file. */
|
||||||
case 'w':
|
case 'w':
|
||||||
/*sed_cmd->sw_last_char =*/ puts_maybe_newline(
|
puts_maybe_newline(
|
||||||
pattern_space, sed_cmd->file,
|
pattern_space, sed_cmd->sw_file,
|
||||||
last_char);
|
&sed_cmd->sw_last_char, last_gets_char);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
/* Read next line from input */
|
/* Read next line from input */
|
||||||
case 'n':
|
case 'n':
|
||||||
if (!bbg.be_quiet)
|
if (!bbg.be_quiet)
|
||||||
sed_puts(pattern_space, last_char);
|
sed_puts(pattern_space, last_gets_char);
|
||||||
if (next_line) {
|
if (next_line) {
|
||||||
free(pattern_space);
|
free(pattern_space);
|
||||||
pattern_space = next_line;
|
pattern_space = next_line;
|
||||||
last_char = next_last_char;
|
last_gets_char = next_gets_char;
|
||||||
next_line = get_next_line(&next_last_char);
|
next_line = get_next_line(&next_gets_char);
|
||||||
linenum++;
|
linenum++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1037,8 +1037,8 @@ restart:
|
||||||
pattern_space = realloc(pattern_space, len + strlen(next_line) + 2);
|
pattern_space = realloc(pattern_space, len + strlen(next_line) + 2);
|
||||||
pattern_space[len] = '\n';
|
pattern_space[len] = '\n';
|
||||||
strcpy(pattern_space + len+1, next_line);
|
strcpy(pattern_space + len+1, next_line);
|
||||||
last_char = next_last_char;
|
last_gets_char = next_gets_char;
|
||||||
next_line = get_next_line(&next_last_char);
|
next_line = get_next_line(&next_gets_char);
|
||||||
linenum++;
|
linenum++;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1093,7 +1093,7 @@ restart:
|
||||||
strcat(pattern_space, "\n");
|
strcat(pattern_space, "\n");
|
||||||
if (bbg.hold_space)
|
if (bbg.hold_space)
|
||||||
strcat(pattern_space, bbg.hold_space);
|
strcat(pattern_space, bbg.hold_space);
|
||||||
last_char = '\n';
|
last_gets_char = '\n';
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1125,7 +1125,7 @@ restart:
|
||||||
{
|
{
|
||||||
char *tmp = pattern_space;
|
char *tmp = pattern_space;
|
||||||
pattern_space = bbg.hold_space ? : xzalloc(1);
|
pattern_space = bbg.hold_space ? : xzalloc(1);
|
||||||
last_char = '\n';
|
last_gets_char = '\n';
|
||||||
bbg.hold_space = tmp;
|
bbg.hold_space = tmp;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -1140,7 +1140,7 @@ restart:
|
||||||
/* 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')
|
||||||
or if the line was suppressed (ala 'd'elete) */
|
or if the line was suppressed (ala 'd'elete) */
|
||||||
if (!bbg.be_quiet)
|
if (!bbg.be_quiet)
|
||||||
sed_puts(pattern_space, last_char);
|
sed_puts(pattern_space, last_gets_char);
|
||||||
|
|
||||||
/* Delete and such jump here. */
|
/* Delete and such jump here. */
|
||||||
discard_line:
|
discard_line:
|
||||||
|
|
|
@ -139,6 +139,10 @@ testing "sed selective matches noinsert newline" \
|
||||||
testing "sed clusternewline" \
|
testing "sed clusternewline" \
|
||||||
"sed -e '/one/a 111' -e '/two/i 222' -e p input -" \
|
"sed -e '/one/a 111' -e '/two/i 222' -e p input -" \
|
||||||
"one\none\n111\n222\ntwo\ntwo" "one" "two"
|
"one\none\n111\n222\ntwo\ntwo" "one" "two"
|
||||||
|
testing "sed subst+write" \
|
||||||
|
"sed -e 's/i/z/' -e 'woutputw' input -; echo -n X; cat outputw" \
|
||||||
|
"thzngy\nagaznXthzngy\nagazn" "thingy" "again"
|
||||||
|
rm outputw
|
||||||
|
|
||||||
# Test end-of-file matching behavior
|
# Test end-of-file matching behavior
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue