7

Example task: if a line contains foo, replace it with bar, otherwise append baz to the line.

sed -e s/foo/bar/ -e s/$/baz/ doesn't work, as the second command gets executed whether or not the first one matches. Is there a way to tell sed to go to the next line after a match?

4 Answers 4

15

You can use the t command without a label to start next cycle on successful substitution

$ cat ip.txt 
a foo 123
xyz
fore
1foo

$ sed -e 's/foo/bar/' -e t -e 's/$/baz/' ip.txt
a bar 123
xyzbaz
forebaz
1bar

From manual:

t label (test)

Branch to label only if there has been a successful substitution since the last input line was read or conditional branch was taken. The label may be omitted, in which case the next cycle is started.

1
  • 2
    The best solution. Readable, extensible, and non-redundant! Commented Jun 24, 2018 at 15:26
3

A more robust way with awk script:

awk '{ if (/foo/) gsub(/foo/, "bar"); else $0 = $0 "baz" }1' file

Or even shorter:

awk '{ if (!gsub(/foo/, "bar")) $0 = $0 "baz"; }1' file
2

Try this:

sed -e '/foo/!s/$/baz/g' -e s/foo/bar/g
7
  • Thanks! That does solve the stated example task, though I don't see a way to expand it to more strings (i.e. replace a1 with a2, or else replace b1 with b2, or else replace c1 with c2...) Commented Jun 24, 2018 at 15:05
  • Also, it means typing the first pattern ("foo") twice, which may not be trivial in practice. Commented Jun 24, 2018 at 15:06
  • Please update your question.
    – Siva
    Commented Jun 24, 2018 at 15:07
  • The question looks fine to me, AFAICS you simply answered only a part of it without considering the last part ("Is there a way to tell sed to go to the next line after a match?"). If you'd like to suggest an edit, I'll approve it. Commented Jun 24, 2018 at 15:10
  • try my edited answer.
    – Siva
    Commented Jun 24, 2018 at 15:12
1

Figured it out:

sed -e '/foo/{s//bar/;p;D};s/$/baz/' 

Explanation:

  • Multiple sed commands can be chained with ; and grouped with { ... }.
  • /foo/ selects all lines containing the pattern foo.
  • s//bar/ is the same as s/foo/bar/ - an empty pattern means to repeat the last search (in this case, foo).
  • p means to print the pattern space (the line after the substitution).
  • D means to clear the current line, and go to the next one.
  • s/$/baz/ will perform the second part of the task, i.e. append baz to lines which the previous rule left to pass through.
3
  • Technically, D strips off the first line off the pattern space and only goes to the next line if the pattern space becomes empty as a result. It would always be the case here as you never add any extra line to the pattern space, but it would still make more sense to simply use d instead. Commented Jun 25, 2018 at 7:14
  • Note that that code is not portable. Portably, you'd need a ; between D and }, and you can't have anything after }, you'd need sed -e '/foo{s//bar;p;d;}' -e 's/$/baz/' Commented Jun 25, 2018 at 7:15
  • @StéphaneChazelas there's a missing / after the foo. Note we can also use the b branch command here: sed -e '/foo/{s//bar/' -e b -e '}' -e 's/$/baz/' Commented Jun 25, 2018 at 8:12

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .