0

I am looking for a solution that would allow me to search text files on a linux server that would look a file and find a pattern such as:

Text 123
Blue Green

And then replaces it with one line, every time it finds it in a file...

Order Blue Green

I am not sure what would be the easiest way to solve this. I have seen many guides using SED but only for finding one line and replacing it.

Big Jon
  • 11
  • 4

2 Answers2

1

You ask about sed, here is an answer in sed.
Let me mention however, that while sed is fun for this kind of exercise, you probably should choose something else, more flexible and easier to learn; perl for example.

  • look for first line /Text 123/
  • when found start a loop :a
    • concat next line N
    • replace twins of searched text with single copy and print it
      s/Text 123\nText 123/Text 123/p;
    • loop while that replaces ta;
  • try to replace s///
  • rely on concat being printed unchanged if replace does not trigger

Code:

sed "/Text 123/{:a;N;s/Text 123\nText 123/Text 123/p;ta;s/Text 123\nBlue Green/Order Blue Green/}"

Test input:

Text 123
Do not replace
Lala
Text 123
Blue Green
lulu
Text 123
Do not replace either
Text 123
Text 123
Blue Green
preceding should be replaced

Output:

Text 123
Do not replace
Lala
Order Blue Green
lulu
Text 123
Do not replace either
Text 123
Order Blue Green
preceding should be replaced

Platform: Windows and GNU sed version 4.2.1

Note: On that platform the sed line allows to use the environment variables for the two text fragments, which you probably want to do:

sed "/%EnvVar2%/{:a;N;s/%EnvVar2%\n%EnvVar2%/%EnvVar2%/p;ta;s/%EnvVar2%\n%EnvVar%/Order %EnvVar%/}"

Platform2:
still Windows
using bash GNU bash, version 3.1.17(1)-release (i686-pc-msys)
GNU sed version 4.2.1 (same)

On this platform, variables can e.g. be used like:

sed "/${EnvVar2}/{:a;N;s/${EnvVar2}\n${EnvVar2}/${EnvVar2}/p;ta;s/${EnvVar2}\n${EnvVar}/Order ${EnvVar}/}"

On this platform it is important to use "..." in order to be able to use variables,
it does not work with '...'.

As @edMorton has hinted, on all platforms be careful however with trying to replace (using variables) text which looks like using a variable. E.g. with "Text $123" in bash. In that case, not using variables but trying to replace text which looks like variables, using '...' instead of "..." is the way to go.

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
  • Always enclose scripts in single quotes unless you NEED double quotes for some reason (e.g. to let a variable expand) and that would fail if `Text 123` occurred on the line before the 2-line block the OP wants to replace. This simply is not a job for sed. – Ed Morton Jun 14 '17 at 23:48
  • @EdMorton Did you test your statment of failure? I did test my script, it does work with input having "Text 123" before and after the occurrence to replace. (On windows, with sed GNU sed version 4.2.1). – Yunnosch Jun 15 '17 at 05:15
  • @EdMorton Do you mean the script fails with `"..."` for some cases in which it would work with `'...'`? Can you give an example for such a test case? – Yunnosch Jun 15 '17 at 05:21
  • @EdMorton I tried `'/Text 123/{N;s/Text 123\n%EnvVar%/Order Blue Green/}'` and `"/Text 123/{N;s/Text 123\n%EnvVar%/Order Blue Green/}"`. Both expad the environment variable and have identical output. – Yunnosch Jun 15 '17 at 05:25
  • @EdMorton Believe it or not, I frequently find myself in the situation to have sed available but not awk. This means that sometimes (not usually, I admit) sed is a tool, even if harder to use, but awk is not. That is why I usually accept OPs looking for sed solutions. You are right however, mentioning less painful learning goals is worth commenting. So I did. – Yunnosch Jun 15 '17 at 05:47
  • @Yannosch 1) yes I did test it. It cannot possibly work as the `N` will consume the second `Text 123` when it finds the first `Text 123` so there is no possible way for the script to find `Text 123` and have `N` append `Blue Green` to it in the hold space (or whatever that's called). 2) Try your double-quoted script if the text to be searched for just happened to be `Text $123` instead of `Text 123` and it will fail - use single quotes unless you NEED double quotes. The OP is on LiInux, not Windows. – Ed Morton Jun 15 '17 at 13:10
  • "Cannot possibly work" versus "tested and works fine". Hmmm, you lost me there. – Yunnosch Jun 15 '17 at 13:13
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/146767/discussion-between-yunnosch-and-ed-morton). – Yunnosch Jun 15 '17 at 13:17
  • @edMorton What a pity if you are not interested in discussing it with me, I would like to understand. Write your own answer, please. I will study it with interest. – Yunnosch Jun 15 '17 at 13:21
  • Ah, good. Then edit that one to show the solution in awk and the solution in sed, with details on which pitfalls you aoivded how. – Yunnosch Jun 15 '17 at 13:25
  • No, you didn't. Look, create your sample input using this `printf 'Test 123\nTest 123\nBlue Green\n' > file` then run your sed command on it and notice that the 3rd line remains unchanged. – Ed Morton Jun 15 '17 at 13:36
  • Oh yes, I can see that input is interesting. Sorry, I misunderstood your not incorrect description of that. I will work on that. – Yunnosch Jun 15 '17 at 13:40
  • Good to hear `that would fail if "Text 123" occurred on the line before the 2-line block` is a "not incorrect" description of the problem). Some might argue that `correct` is a better term than `not incorrect` :-). – Ed Morton Jun 15 '17 at 13:48
  • Hey, spare my feelings please. I read totally, hell-deservingly, stupidly, wrongly, "a line before", instead of the perfect "the line before". That unbelievable failure allowed me to only think of the test input I provided. For morons like me, a little stress via "immediatly preceeding" would have been helpful and foolproof, instead of only correct. Or maybe giving the test input you were thinking of, literally.@EdMorton – Yunnosch Jun 15 '17 at 14:06
  • 1
    Interestingly your updated solution seems to work with the input file you provided but not with the input file that the `printf` I suggested produces. I've no idea why - I don't read sed loops... – Ed Morton Jun 15 '17 at 14:11
  • Interesting indeed. Lets wait for OP to provide the test input they care about. I did however test with "LF"-only line endings and the windows "CRLF". – Yunnosch Jun 15 '17 at 14:12
  • I'm all for "let's see if it's a real problem" but we should always add a statement of what the caveats are for a solution if there are any, especially when they're non-obvious as in this case. Having said that - I don't know what layout of input the script will and won't work for, all I know is it worked for 1 test case but not the other test case, so I wouldn't know how to give the OP a heads up of what to watch out for and take into consideration. If you do, it'd be good to just update the answer to say "this will/won't work if ". – Ed Morton Jun 15 '17 at 14:18
  • 1
    @EdMorton I actually like that you picked up the difference between "not incorrect" and "correct". :-) – Yunnosch Jun 15 '17 at 14:18
  • I am not unflattered that you noticed :-). – Ed Morton Jun 15 '17 at 14:18
  • @EdMorton I also am a fan of "heads up"s. I think ticking your corresponding comment serves that purpose, at least together with my platform statements. – Yunnosch Jun 15 '17 at 14:22
0

sed is for simple substitutions on individual lines, that is all. If you find yourself trying to use constructs other than s, g, and p (with -n) then you are on the wrong track as all other sed constructs became obsolete in the mid-1970s when awk was invented.

Your problem is not doing substitutions on individual lines, it's on a multi-line record and to do that with GNU awk for multi-char RS is:

$ awk -v RS='^$' -v ORS= '{gsub(/Text 123\nBlue Green/,"Order Blue Green")}1' file
Order Blue Green

but there are several other approaches depending on your real needs.

Ed Morton
  • 188,023
  • 17
  • 78
  • 185