Frank Leonhardt wrote in his OP:
> I've got a file called example.txt:
>
> Line 1
> Line 2
> Line 3
>
> I'm trying to add "New Line" after "Line 2"
> This implies the newline is required but I'm struggling with finding a
> sane syntax here. A pointer to some better documentation would be welcome!
Frank Leonhardt also wrote:
> TLDR: The methods and syntax I was using are fine on GNU sed but
> BSD sed appears to require a newline and there's no way around it.
Various factors come into play:
(A) Using sed completely on the command line combines a shell
language with a programming language (i.e. scripting language).
(B) GNU sed is not equal to FreeBSD sed, is not equal to POSIX sed.
(I’m not aware if there’s a POSIX sed reference implementation.)
(C) Man page or standardisation texts (i.e. POSIX) are geared for
reference. Their inherent terseness often requires very careful
reading; subtleties can easily be overlooked.
In addition to the last item, sometimes an option is absent from its
man page, as is the case with gsed(1). A portable solution across
various OS-es is often needed.
Below, you'll find:
- a different portable sed solution;
- discussion of the two GNU sed formats of the 'a' command,
the problem of the --posix option;
- another command line solution, not using sed,
POSIX 'certified'.
A small improvement suggestion for the FreeBSD sed(1)
man page concludes this e-mail.
Various solutions exist that present "in-place editing", however,
under the hood, this is likely defeated everywhere. Using any
other language that uses a temporary file explicitly without
in-place editing can work just as well. I'll use FreeBSD sed, and
gsed (GNU sed) as installed from textproc/gsed, running on 14.3-R.
sed -E -i.bak '/2/{H;g;s/(\n)(.*)/\2\1New Line/;}' example.txt
There is no shell continuation and it works for sed and GNU sed;
example run:
$ seq 3|sed 's/./Line &/' > i; sed -E -i.bak
'/2/{H;g;s/(\n)(.*)/\2\1New Line/;}' i; cat i
Line 1
Line 2
New Line
Line 3
=== gsed's 'a' command – two formats
Bob Proulx in response to Frank's posting:
> Both of these should work as far as I know:
>
> sed -i.bak '/Line 2/a\New Line' example.txt
That \N is not valid syntax.
> sed -i.bak -e '/Line 2/a\" -e "New Line' example.txt
That \" is not valid syntax.
QUOTE_END
Taking the following as starting point:
gsed -i.bak '/Line 2/a\New Line' example.txt
gsed -i.bak -e '/Line 2/a\" -e "New Line' example.txt
Both are valid GNU syntax, but they may work differently
then expected. I find the GNU syntax description not very precise
and, as we'lll see, --posix isn't all that strict.
GNU sed has two formats of the a command:
(1) a text
(2) a\
text
Remarkably, format (1), a GNU extension, is absent from the gsed(1) man page:
https://man.freebsd.org/cgi/man.cgi?query=gsed&apropos=0&sektion=1&manpath=FreeBSD+Ports+14.3&arch=default&format=html
The GNU html web page and its examples describes both formats:
https://www.gnu.org/software/sed/manual/sed.html#Less-Frequently_002dUsed-Commands
Furthermore:
As a GNU extension, the a command and text can be separated into two
-e parameters, enabling easier scripting:
[...]
QUOTE_END
> sed -i.bak '/Line 2/a\New Line' example.txt
In Frank's referenced command, the '\' however, designates
the use of a literal character immediately following. Thus,
equivalent to:
gsed -i.bak '/Line 2/a New Line' example.txt
I don't see the format without the space after the 'a'
spelled out in its syntax description html web page though.
With format (1) and using weak quotes, it can easily be
shown that the '\' does not designate anything related
to the append command syntax in a strict sense.
Forcing the literal use of a dollar character ($):
# seq 3|sed 's/./Line &/' > i; gsed -i.bak "/Line 2/a\${USER}New Line" i; cat i
Line 1
Line 2
${USER}New Line
Line 3
Removing the '\' enables the variable reference mechanism:
# seq 3|sed 's/./Line &/' > i; gsed -i.bak "/Line 2/a${USER}New Line" i; cat i
Line 1
Line 2
ericNew Line
Line 3
Gsed can enforce POSIX behavior; gsed(1) excels in being brief.
https://www.gnu.org/software/sed/manual/sed.html#Command_002dLine-Options-1
The html web page offers a lengthier explanation:
--posix
GNU sed includes several extensions to POSIX sed. In order to simplify
writing portable scripts, this option disables all the extensions that
this manual documents, including additional commands. Most of the
extensions accept sed programs that are outside the syntax mandated by
POSIX, but some of them (such as the behavior of the N command described
in Reporting Bugs) actually violate the standard. If you want to disable
only the latter kind of extension, you can set the POSIXLY_CORRECT
variable to a non-empty value.
QUOTE_END
The following GNU sed works as expected:
$ seq 3|sed 's/./Line &/' > i; gsed -i.bak "/Line 2/a New Line" i; cat i
Line 1
Line 2
New Line
Line 3
Here, --posix is enforcing posix behavior, as expected:
$ seq 3|sed 's/./Line &/' > i; gsed --posix -i.bak "/Line 2/a New Line" i; cat i
gsed: -e expression #1, char 11: expected \ after `a', `c' or `i'
Line 1
Line 2
Line 3
Of course FreeBSD sed is in agreement:
$ seq 3|sed 's/./Line &/' > i; sed -i.bak "/Line 2/a New Line" i; cat i
sed: 1: "/Line 2/a New Line
": command a expects \ followed by text
Line 1
Line 2
Line 3
gsed seems to be fooled easily, not as expected:
$ seq 3|sed 's/./Line &/' > i; gsed --posix -i.bak "/Line 2/a\New Line" i; cat i
Line 1
Line 2
New Line
Line 3
Also, not as expected:
$ seq 3|sed 's/./Line &/' > i; gsed --posix -i.bak "/Line 2/a\ New
Line" i; cat i
Line 1
Line 2
New Line
Line 3
A well-placed backslash defeats enforcement of POSIX behavior.
=== ex (vi) solution
As mentioned previously by Bob, ed(1) can be used too; however,
that makes use of a here document, spanning multiple lines. From
a portability standpoint this could work but, I imagine ed isn't
that widely available.
Sed, though likely more widely available, 'suffers' from multiple
implementations, that are not equally available on different OS-es.
Another contender for solving this dire problem is the venerable vi(1).
Although multiple variations of vi exist as well, I'm inclined to think
(haven't taken an in-depth look) that all support the following solution:
ex -sc ':3y | :3pu | :s/.*/blup/ | :w | :q' example.txt
When using the ex editor of textproc/vim on FreeBSD, where vi
is already installed as part of base, you'll need:
vim -es -c ':/2/y | :/2/pu | :s/.*/New Line/ | :w | :q' example.txt
Example run:
$ seq 3|sed 's/./Line &/' > i; ex -sc ':/2/y|:/2/pu|:s/.*/New
Line/|:w|:q' i; cat i
Line 1
Line 2
New Line
Line 3
In-place editing of sed is not part of POSIX; ex(1), however,
is defined in POSIX:
https://pubs.opengroup.org/onlinepubs/9799919799/utilities/ex.html
Generally, I just find sed properties more flexible and usable
then the above ex script. For example, using a non-successful
sed match gives:
$ seq 3|sed 's/./Line &/' > i; sed -E -i.bak
'/22/{H;g;s/(\n)(.*)/\2\1New Line/;}' i ; cat i
Line 1
Line 2
Line 3
With ex, one is thrown into an active edit session:
$ seq 3|sed 's/./Line &/' > i; ex -sc ':/22/y|:/22/pu|:s/.*/New
Line/|:w|:q' i; cat i
-c option, 1: Pattern not found
-c option, 1: Ex command failed: pending commands discarded
Press Enter to continue:
=== FreeBSD sed(1) man page
FreeBSD sed(1) states:
QUOTE
[1addr]a\
text Write text to standard output immediately before
each attempt
to read a line of input, whether by executing the
"N" function
or by beginning a new cycle.
QUOTE_END
There is a very subtle difference with, for example, the p command:
QUOTE
[2addr]p
Write the pattern space to standard output.
QUOTE_END
With the 'a' command the textual part start on the second line because
'text' is small enough to leave enough white space between it and the
first words 'Write text'. With the 'p' command, however, the textual
part starts on the second line because '[2addr]p' is too wide for the
textual part to start on the same line with as the first words 'Write the'.
https://pubs.opengroup.org/onlinepubs/9799919799/utilities/sed.html#tag_20_109_13_03
POSIX, however, has a slightly different layout, that exemplifies the
mandatory new line more clearly:
QUOTE
[1addr]a\
text
Write text to standard output as described previously.
QUOTE_END
Similar layout is being used as shown in my hardcopy of "sed & awk"
first edition by Dale Dougherty.
QUOTE
a [address]a\
text
Append text following each line matched by address. [...]
QUOTE_END
I suggest a small layout change for the textual part of the
description of the 'a' command,
insert an extra line:
[1addr]a\
text
Write text to standard output immediately before
each attempt
to read a line of input, whether by executing the
"N" function
or by beginning a new cycle.
The same change applies to the 'c' and 'i' command.
Eric