4

I'm trying to substitute (with sed) an entire line containing a specific word and the newline at the end. Here the testfile:

this # target for substitution
this is a test
another test?

Now, I already posted here, and from the linked post, I understand how to do this in some way:

sed 's/^this$/test/g' testfile

That works, or at least it seems so, because the newline at the end of the word this is still there:

test # target for substitution but newline is still there
this is a test
another test?

Given the above, I'm also fully aware sed can't match the newline directly (although I do recall that I could use '\n' in certain version of sed, but that's beside the point).

I do know how to at least delete the entire word/line and the newline:

sed '/^this$/d' testfile

Except I need to substitute it instead.

How can I do this? (with sed preferably)

Nordine Lotfi
  • 2,200
  • 12
  • 45
  • 1
    When you also remove the newline, you effectively substitute the lines `this` and `this is a test` by a single line `testthis is a test`. Is that correct? It's not easy to join two lines together in sed; see examples in the [manual](https://www.gnu.org/software/sed/manual/sed.html#Joining-lines). You could use awk, which can read the next line: `awk -v replacement=test '/^this$/ { getline; print replacement $0 }' testfile` – berndbausch May 10 '21 at 05:30
  • Yes, that is absolutely correct (and coincidentally exactly what i want here) @berndbausch And I always thought it feasible to join two lines together in sed, especially since i vaguely recall doing it once, but don't quote me on that as I'm unsure and don't remember what i did in sed for this...Lastly I really prefer doing this in sed _if possible_, otherwise awk might be fine if that's really the only way – Nordine Lotfi May 10 '21 at 05:34
  • What if you have two consecutive lines having `this` as the content, the `awk` solution above will replace only the first such line – Sundeep May 10 '21 at 05:38
  • Joining lines seems to be feasible, but my simple mind doesn't penetrate the logic in the manual. I think the Perl answer below is the nicest solution (I did not think I would ever call anything related to Perl "nice"). – berndbausch May 10 '21 at 05:39
  • @Sundeep yes it requires a bit more work. Your solution is much superior. – berndbausch May 10 '21 at 05:40
  • I did managed to join lines in my own answer, although didn't find an easy way to substitute at the same time, unless i use `-e` or `;` and do it before removing the newline... @berndbausch – Nordine Lotfi May 10 '21 at 05:47
  • 1
    Does this work? `awk '{if (/^this$/) {sub(/^this$/, "test"); printf "%s", $0;} else print }' file` – Prabhjot Singh May 10 '21 at 05:56
  • 1
    @berndbausch Why shouldn't it be easy to joing two lines in `sed`? There is the `N` command for this. Nothing more simple. – Philippos May 10 '21 at 06:35
  • 1
    @NordineLotfi In *all* `sed` versions, you can match a newline in the search pattern with `\n`. What you recall applies to the replacement pattern. – Philippos May 10 '21 at 06:38
  • OH, so that's what it is. Thanks for clearing the confusion i had :D @Philippos – Nordine Lotfi May 10 '21 at 06:56
  • @Philippos Simply because I tried it and failed. – berndbausch May 10 '21 at 08:32
  • @berndbausch What did you try? Using the `N` command? See my answer: It works and it's extremely simple. – Philippos May 10 '21 at 08:35
  • I am not asking a question here :) – berndbausch May 10 '21 at 08:36
  • Correct. You are just giving misleading comments. (-; – Philippos May 10 '21 at 08:55

6 Answers6

7

As I understand you, you want to replace a line consisting only of the word this and the following newline by test, so

foo
this
this is a test

should become

foo
testthis is a test

In sed you can do simply join the next line with N and replace everything up to the newline:

sed '/^this$/{N;s/.*\n/test/;}'
Philippos
  • 13,237
  • 2
  • 37
  • 76
5

I'd suggest to use perl here, syntax isn't that different from sed for this case:

$ cat ip.txt
this
this is a test
another test?

$ perl -pe 's/^this\n/XYZ/' ip.txt
XYZthis is a test
another test?
Sundeep
  • 11,753
  • 2
  • 26
  • 57
  • 1
    yeah, too bad you can't `directly` use newline like this, otherwise would make things easier when using sed...Thanks for this alternative – Nordine Lotfi May 10 '21 at 05:43
5

With GNU sed, you can read all lines into memory with -z and do the matching from there, e.g.:

sed -z 's/this\n/test/'
Thor
  • 16,942
  • 3
  • 52
  • 69
  • Interesting. Seems like using the `-z` flag make the use of `\n` work as expected... – Nordine Lotfi May 10 '21 at 22:16
  • 1
    @NordineLotfi: with `-z` sed assumes the data is delimted by NUL chars (`\0`). For normal files this results in the whole file being read into memory before the sed-script is run. – Thor May 11 '21 at 15:56
3

Turn out doing this, or as other have pointed out "joining" lines in sed is possible:

sed ':a;/^this$/{N;s/\n//;ba}' testfile

result:

thisthis is a test
another test?

To also do substitution:

sed 's/^this$/test/g;:a;/^test$/{N;s/\n//;ba}' testfile
testthis is a test
another test?

Taken from this answer.

Nordine Lotfi
  • 2,200
  • 12
  • 45
2

One way using awk by manipulating the output record separator:

$ awk '{ ORS = sub(/^this$/,"FOO") ? "" : RS }1' file

$ sed -e '
    $!N
    s/^this\n/FOO/;t
    P;D
' file
guest_7
  • 5,698
  • 1
  • 6
  • 13
1

Using awk:

awk '/^this$/{$0="test"}1'  file

In this command, if pattern is found then this is replaced by replacement.

The remaining work is to remove newline after replacement because newline is still there. printf would do that work because unlike print this doesn’t add newline at the end by default.

This is:

awk '/^this$/{printf "%s", "test"; next} 1'   file

And this works.

Prabhjot Singh
  • 1,276
  • 1
  • 4
  • 16