ChatGPT解决这个技术问题 Extra ChatGPT

Insert line after match using sed

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"
If one needs capture groups to be used in the inserted line, check this question stackoverflow.com/q/39103787/520567

D
Dessa Simpson

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)


Note that both assume GNU sed. That's not standard sed syntax and won't work with any other sed implementation.
I had trouble getting this to work for me because I was using a different delimiter. After RTFM'ing, I realized I had to start the regex with a \, then the delimiter.
How does it apply ONLY to the first match? it is clear that it append text after the match, but how does it know only to the first match?
This adds the text after every match, for me.
Ist there a way to keep the intends?
S
Stephane Chazelas

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

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.
@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.
Terminal on MacOS (and subsequently a script run in terminal) is also an exception, apparently. I found alternate syntax, but thanks anyway.
@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.
I had so many problems with different versions of sed that I tried the printf...ex version. This works like charm!
S
SLePort

A POSIX compliant one using the s command:

sed '/CLIENTSCRIPT="foo"/s/.*/&\
CLIENTSCRIPT2="hello"/' file

...that even supports inserting new lines. I love it!
See also sed -e '/.../s/$/\' -e 'CLI.../' which would avoid problems with lines that contain byte sequences not forming valid characters.
B
Breezer

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.


Thank you for the answer! First one works like a charm on AIX OS as well.
how do you do this but for multi-line insertion?
POSIX sed supports literal newlines in the replacement string if you "escape" them with a backslash (source). 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.
@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.
s
siwesam

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


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, under the heading for [2addr]s/BRE/replacement/flags). This means your sed command has to span multiple lines in the terminal.
Another option to get a literal newline in the replacement string is to use the ANSI-C quoting feature in Bash, as mentioned in one of the other answers.
C
Corentin Limier

The awk variant :

awk '1;/CLIENTSCRIPT=/{print "CLIENTSCRIPT2=\"hello\""}' file

For anyone wondering about the 1;, see Why does “1” in awk print the current line?
C
Caleb

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].


m
momonari8

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.