>From d7f65e90b5b497a452212e7e0aab4dbe1a659087 Mon Sep 17 00:00:00 2001 From: Assaf Gordon Date: Wed, 11 Aug 2021 11:33:48 -0600 Subject: [PATCH] sed: allow '0rFILE' (insert FILE before the first line) The 'r' command can be used with address zero, effectively prepending a file to the beginning of the input file, e.g.: sed '0rA.TXT' B.TXT > C.TXT is equivalent to: cat A.TXT B.TXT > C.TXT With "sed -i", this allows safe in-place prepending of files. A typical example would be adding a license header to multiple source files: sed -i '0rLICENSE' *.c *.h find -iname '*.cpp' | xargs sed -i '0rLICENSE' A current cumbersome alternative is: sed -i -e 'x;${p;x};1rA.TXT' -e '1d' B.TXT ** TODO: update documentation ** * NEWS: Mention new feature. * sed/sed.h (struct readcmd): New struct. (struct sed_cmd): Use new struct instead of a char* for the filename. * sed/compile.c (compile_program): Expand conditional detecting invalid usage of "0" address to allow "0r"; Adjust '0r' to '1r' with prepending (instead of appending). * sed/execute.c (execute_program): 'r' command: support prepending. * sed/debug.c (debug_print_function): Use the new 'struct readcmd'. * testsuite/cmd-0r.sh: New test. * testsuite/local.mk (TESTS): Add new test. --- NEWS | 5 ++++ sed/compile.c | 19 ++++++++++-- sed/debug.c | 2 +- sed/execute.c | 13 +++++++-- sed/sed.h | 7 ++++- testsuite/cmd-0r.sh | 70 +++++++++++++++++++++++++++++++++++++++++++++ testsuite/local.mk | 1 + 7 files changed, 110 insertions(+), 7 deletions(-) create mode 100755 testsuite/cmd-0r.sh diff --git a/NEWS b/NEWS index 45593af..e415427 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,11 @@ GNU sed NEWS -*- outline -*- using the R command to read an input line of length longer than 2GB can no longer trigger an out-of-bounds memory read. +** New Features + + The 'r' command now accepts address 0, allowing inserting a file before + the first line. + * Noteworthy changes in release 4.8 (2020-01-14) [stable] diff --git a/sed/compile.c b/sed/compile.c index 6d12f74..1d7d8ee 100644 --- a/sed/compile.c +++ b/sed/compile.c @@ -1021,7 +1021,8 @@ compile_program (struct vector *vector) if ((cur_cmd->a1->addr_type == ADDR_IS_NUM && cur_cmd->a1->addr_number == 0) - && ((!cur_cmd->a2 || cur_cmd->a2->addr_type != ADDR_IS_REGEX) + && ((!cur_cmd->a2 && ch != 'r') + || (cur_cmd->a2 && cur_cmd->a2->addr_type != ADDR_IS_REGEX) || posixicity == POSIXLY_BASIC)) bad_prog (_(INVALID_LINE_0)); } @@ -1196,7 +1197,21 @@ compile_program (struct vector *vector) b = read_filename (); if (strlen (get_buffer (b)) == 0) bad_prog (_(MISSING_FILENAME)); - cur_cmd->x.fname = xstrdup (get_buffer (b)); + cur_cmd->x.readcmd.fname = xstrdup (get_buffer (b)); + + /* Adjust '0rFILE' command to '1rFILE' in prepend mode */ + if (cur_cmd->a1 + && cur_cmd->a1->addr_type == ADDR_IS_NUM + && cur_cmd->a1->addr_number == 0 + && !cur_cmd->a2) + { + cur_cmd->a1->addr_number = 1; + cur_cmd->x.readcmd.append = false; + } + else + { + cur_cmd->x.readcmd.append = true; + } free_buffer (b); break; diff --git a/sed/debug.c b/sed/debug.c index 0530496..99f8b87 100644 --- a/sed/debug.c +++ b/sed/debug.c @@ -363,7 +363,7 @@ debug_print_function (const struct vector *program, const struct sed_cmd *sc) case 'r': putchar (' '); - fputs (sc->x.fname, stdout); + fputs (sc->x.readcmd.fname, stdout); break; case 'R': diff --git a/sed/execute.c b/sed/execute.c index bae5735..defa376 100644 --- a/sed/execute.c +++ b/sed/execute.c @@ -1509,10 +1509,17 @@ execute_program (struct vector *vec, struct input *input) return cur_cmd->x.int_arg == -1 ? 0 : cur_cmd->x.int_arg; case 'r': - if (cur_cmd->x.fname) + if (cur_cmd->x.readcmd.fname) { - struct append_queue *aq = next_append_slot (); - aq->fname = cur_cmd->x.fname; + if (cur_cmd->x.readcmd.append) + { + struct append_queue *aq = next_append_slot (); + aq->fname = cur_cmd->x.readcmd.fname; + } + else + { + print_file (cur_cmd->x.readcmd.fname, output_file.fp); + } } break; diff --git a/sed/sed.h b/sed/sed.h index 64bf17c..78117e7 100644 --- a/sed/sed.h +++ b/sed/sed.h @@ -57,6 +57,11 @@ struct regex { char re[1]; }; +struct readcmd { + char *fname; + bool append; /* true: append (default); false: prepend (gnu extension) */ +}; + enum replacement_types { REPL_ASIS = 0, REPL_UPPERCASE = 1, @@ -158,7 +163,7 @@ struct sed_cmd { countT jump_index; /* This is used for the r command. */ - char *fname; + struct readcmd readcmd; /* This is used for the hairy s command. */ struct subst *cmd_subst; diff --git a/testsuite/cmd-0r.sh b/testsuite/cmd-0r.sh new file mode 100755 index 0000000..bf48e6a --- /dev/null +++ b/testsuite/cmd-0r.sh @@ -0,0 +1,70 @@ +#!/bin/sh +# Test '0rFILE' command + +# Copyright (C) 2021 Free Software Foundation, Inc. + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +. "${srcdir=.}/testsuite/init.sh"; path_prepend_ ./sed +print_ver_ sed + +cat <<\EOF >in1 || framework_failure_ +HELLO +WORLD +EOF + +cat <<\EOF >in2 || framework_failure_ +1 +2 +3 +EOF + +cat <<\EOF >exp1 || framework_failure_ +HELLO +WORLD +1 +2 +3 +EOF + +cat <<\EOF >exp2 || framework_failure_ +1 +HELLO +WORLD +2 +HELLO +WORLD +3 +EOF + +cat <<\EOF> exp-err-addr0 || framework_failure_ +sed: -e expression #1, char 4: invalid usage of line address 0 +EOF + +# Typical usage +sed '0rin1' in2 >out1 || fail=1 +compare_ exp1 out1 || fail=1 + +# Ensure no regression for '0,/REGEXP/r' +sed '0,/2/rin1' in2 >out2 || fail=1 +compare_ exp2 out2 || fail=1 + +# Ensure '0r' doesn't accept a numeric address range +returns_ 1 sed '0,4rin1' in2 2>err3 || fail=1 +compare_ exp-err-addr0 err3 || fail=1 + +# Test with -i +sed -i '0rin1' in2 || fail=1 +compare_ exp1 in2 || fail=1 + +Exit $fail diff --git a/testsuite/local.mk b/testsuite/local.mk index 8ffc505..7561c03 100644 --- a/testsuite/local.mk +++ b/testsuite/local.mk @@ -47,6 +47,7 @@ T = \ testsuite/bug32271-1.sh \ testsuite/bug32271-2.sh \ testsuite/cmd-l.sh \ + testsuite/cmd-0r.sh \ testsuite/cmd-R.sh \ testsuite/colon-with-no-label.sh \ testsuite/comment-n.sh \ -- 2.20.1