321

For some reason I can't seem to find a straightforward answer to this and I'm on a bit of a time crunch at the moment. How would I go about inserting a choice line of text after the first line matching a specific string using the sed command. I have ...

CLIENTSCRIPT="foo"
CLIENTFILE="bar"

And I want insert a line after the CLIENTSCRIPT= line resulting in ...

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"
ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
user2150250
  • 4,797
  • 11
  • 36
  • 44
  • 1
    If one needs capture groups to be used in the inserted line, check this question https://stackoverflow.com/q/39103787/520567 – akostadinov Feb 18 '21 at 22:39

8 Answers8

463

Try doing this using GNU sed:

sed '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

if you want to substitute in-place, use

sed -i '/CLIENTSCRIPT="foo"/a CLIENTSCRIPT2="hello"' file

Output

CLIENTSCRIPT="foo"
CLIENTSCRIPT2="hello"
CLIENTFILE="bar"

Doc

  • see sed doc and search \a (append)
Dessa Simpson
  • 1,232
  • 1
  • 15
  • 30
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
67

Note the standard sed syntax (as in POSIX, so supported by all conforming sed implementations around (GNU, OS/X, BSD, Solaris...)):

sed '/CLIENTSCRIPT=/a\
CLIENTSCRIPT2="hello"' file

Or on one line:

sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file

(-expressions (and the contents of -files) are joined with newlines to make up the sed script sed interprets).

The -i option for in-place editing is also a GNU extension, some other implementations (like FreeBSD's) support -i '' for that.

Alternatively, for portability, you can use perl instead:

perl -pi -e '$_ .= qq(CLIENTSCRIPT2="hello"\n) if /CLIENTSCRIPT=/' file

Or you could use ed or ex:

printf '%s\n' /CLIENTSCRIPT=/a 'CLIENTSCRIPT2="hello"' . w q | ex -s file
Stephane Chazelas
  • 5,859
  • 2
  • 34
  • 31
  • 2
    I may be wrong, but the current `sed -e '/CLIENTSCRIPT=/a\' -e 'CLIENTSCRIPT2="hello"' file` escapes the quote at the end of the first parameter and breaks the command. – Abandoned Cart Aug 06 '18 at 17:22
  • 1
    @AbandonedCart, in shells of the Bourne, `csh` or `rc` family, `'...'` are strong quotes inside which backslash is not special. The only exception that I know is the `fish` shell. – Stephane Chazelas Aug 06 '18 at 19:34
  • 1
    Terminal on MacOS (and subsequently a script run in terminal) is also an exception, apparently. I found alternate syntax, but thanks anyway. – Abandoned Cart Aug 06 '18 at 19:39
  • 1
    @AbandonedCart, that's something else. That's macOS `sed` not being POSIX compliant here. That makes my statement about it being portable incorrect (for the one line variant). I'll ask the opengroup for confirmation if it's indeed a non-conformance or a misinterpretation of the standard on my part. – Stephane Chazelas Aug 06 '18 at 20:38
  • I had so many problems with different versions of sed that I tried the `printf...ex` version. This works like charm! – Matthias Bohlen Aug 01 '21 at 12:12
20

A POSIX compliant one using the s command:

sed '/CLIENTSCRIPT="foo"/s/.*/&\
CLIENTSCRIPT2="hello"/' file
SLePort
  • 15,211
  • 3
  • 34
  • 44
20

Sed command that works on MacOS (at least, OS 10) and Unix alike (ie. doesn't require gnu sed like Gilles' (currently accepted) one does):

sed -e '/CLIENTSCRIPT="foo"/a\'$'\n''CLIENTSCRIPT2="hello"' file

This works in bash and maybe other shells too that know the $'\n' evaluation quote style. Everything can be on one line and work in older/POSIX sed commands. If there might be multiple lines matching the CLIENTSCRIPT="foo" (or your equivalent) and you wish to only add the extra line the first time, you can rework it as follows:

sed -e '/^ *CLIENTSCRIPT="foo"/b ins' -e b -e ':ins' -e 'a\'$'\n''CLIENTSCRIPT2="hello"' -e ': done' -e 'n;b done' file

(this creates a loop after the line insertion code that just cycles through the rest of the file, never getting back to the first sed command again).

You might notice I added a '^ *' to the matching pattern in case that line shows up in a comment, say, or is indented. Its not 100% perfect but covers some other situations likely to be common. Adjust as required...

These two solutions also get round the problem (for the generic solution to adding a line) that if your new inserted line contains unescaped backslashes or ampersands they will be interpreted by sed and likely not come out the same, just like the \n is - eg. \0 would be the first line matched. Especially handy if you're adding a line that comes from a variable where you'd otherwise have to escape everything first using ${var//} before, or another sed statement etc.

This solution is a little less messy in scripts (that quoting and \n is not easy to read though), when you don't want to put the replacement text for the a command at the start of a line if say, in a function with indented lines. I've taken advantage that $'\n' is evaluated to a newline by the shell, its not in regular '\n' single-quoted values.

Its getting long enough though that I think perl/even awk might win due to being more readable.

Breezer
  • 483
  • 3
  • 10
  • 1
    Thank you for the answer! First one works like a charm on AIX OS as well. – abhishek Sep 23 '18 at 01:05
  • how do you do this but for multi-line insertion? – tatsu Jul 04 '19 at 14:32
  • 1
    POSIX sed supports literal newlines in the replacement string if you "escape" them with a backslash ([source](http://man7.org/linux/man-pages/man1/sed.1p.html)). So: `s/CLIENTSCRIPT="foo"/&\ ` **[Enter]** `CLIENTSCRIPT2="hello"/`. The backslash has to be the very last character on the line (no whitespace after it), contrary to how it looks in this comment. – TheDudeAbides Dec 06 '19 at 19:31
  • 1
    @tatsu Newlines in the replacement string of `s///`, as well as those in the `a` and `i` commands can be "escaped," using the same method I describe in my comment above. – TheDudeAbides Dec 06 '19 at 19:33
11

Maybe a bit late to post an answer for this, but I found some of the above solutions a bit cumbersome.

I tried simple string replacement in sed and it worked:

sed 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

& sign reflects the matched string, and then you add \n and the new line.

As mentioned, if you want to do it in-place:

sed -i 's/CLIENTSCRIPT="foo"/&\nCLIENTSCRIPT2="hello"/' file

Another thing. You can match using an expression:

sed -i 's/CLIENTSCRIPT=.*/&\nCLIENTSCRIPT2="hello"/' file

Hope this helps someone

siwesam
  • 463
  • 5
  • 8
  • This works fine on Linux, where GNU sed is default, because it understands `\n` in the replacement string. Plain-vanilla (POSIX) `sed` does _not_ understand `\n` in the replacement string, however, so this won't work on BSD or macOS—unless you've installed GNU sed somehow. With vanilla sed you _can_ embed newlines by "escaping" them—that is, ending the line with a backslash, then pressing **[Enter]** ([reference](http://man7.org/linux/man-pages/man1/sed.1p.html), under the heading for `[2addr]s/BRE/replacement/flags`). This means your sed command has to span multiple lines in the terminal. – TheDudeAbides Dec 06 '19 at 19:37
  • Another option to get a literal newline in the replacement string is to use the [ANSI-C quoting](https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html) feature in Bash, as mentioned in [one of the other answers](https://stackoverflow.com/a/48406504/785213). – TheDudeAbides Dec 06 '19 at 19:37
7

The awk variant :

awk '1;/CLIENTSCRIPT=/{print "CLIENTSCRIPT2=\"hello\""}' file
Corentin Limier
  • 4,946
  • 1
  • 13
  • 24
  • 2
    For anyone wondering about the `1;`, see [Why does “1” in awk print the current line?](https://stackoverflow.com/questions/20262869/why-does-1-in-awk-print-the-current-line) – cherdt Sep 18 '19 at 15:03
  • Thanks, how to only print after first match? – Sumit Jain Sep 21 '22 at 06:44
1

I had a similar task, and was not able to get the above perl solution to work.

Here is my solution:

perl -i -pe "BEGIN{undef $/;} s/^\[mysqld\]$/[mysqld]\n\ncollation-server = utf8_unicode_ci\n/sgm" /etc/mysql/my.cnf

Explanation:

Uses a regular expression to search for a line in my /etc/mysql/my.cnf file that contained only [mysqld] and replaced it with

[mysqld] collation-server = utf8_unicode_ci

effectively adding the collation-server = utf8_unicode_ci line after the line containing [mysqld].

Caleb
  • 188
  • 4
0

I had to do this recently as well for both Mac and Linux OS's and after browsing through many posts and trying many things out, in my particular opinion I never got to where I wanted to which is: a simple enough to understand solution using well known and standard commands with simple patterns, one liner, portable, expandable to add in more constraints. Then I tried to looked at it with a different perspective, that's when I realized i could do without the "one liner" option if a "2-liner" met the rest of my criteria. At the end I came up with this solution I like that works in both Ubuntu and Mac which i wanted to share with everyone:

insertLine=$(( $(grep -n "foo" sample.txt | cut -f1 -d: | head -1) + 1 ))
sed -i -e "$insertLine"' i\'$'\n''bar'$'\n' sample.txt

In first command, grep looks for line numbers containing "foo", cut/head selects 1st occurrence, and the arithmetic op increments that first occurrence line number by 1 since I want to insert after the occurrence. In second command, it's an in-place file edit, "i" for inserting: an ansi-c quoting new line, "bar", then another new line. The result is adding a new line containing "bar" after the "foo" line. Each of these 2 commands can be expanded to more complex operations and matching.

momonari8
  • 51
  • 4