6

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?

14

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! Jun 24 '18 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...) Jun 24 '18 at 15:05
  • Also, it means typing the first pattern ("foo") twice, which may not be trivial in practice. Jun 24 '18 at 15:06
  • Please update your question.
    – Siva
    Jun 24 '18 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. Jun 24 '18 at 15:10
  • try my edited answer.
    – Siva
    Jun 24 '18 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. Jun 25 '18 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/' Jun 25 '18 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/' Jun 25 '18 at 8:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.