1

Following up on my previous question about using Golang's regex to replace between strings. I now have a bit of complexity added to it. Here is what the contexts of my file looks like:

foo:
    blahblah
    MYSTRING=*
bar:
    blah
    blah
    MYSTRING=*

I need to replace what's between MYSTRING= and \n with a string of my choice (like previous stated in the original post). I can do that with:

var re = regexp.MustCompile(`(MYSTRING=).*`)
s := re.ReplaceAllString(content, `${1}stringofmychoice`)

But now I need to match and replace only after a certain occurrence. So that the contents of my file can look something like this:

foo:
    blahblah
    MYSTRING=foostring
bar:
    blah
    blah
    MYSTRING=barstring

ReplaceAllString obviously replaces everything, which is not what I want. Is there a way to only match and replace the first occurrence after a certain string?


For a bit of background about all of this. I'm trying to write a program to edit the contents of a given docker-compose.yml file and its environment variables. I need to edit the environment variable MYSTRING differently depending on what service it's listed under. In the example above, the two different services would be foo and bar.

bli00
  • 2,215
  • 2
  • 19
  • 46
  • Do you have to capture `MYSTRING=` and then use a backreference in the replacement pattern? Can you hardcode `MYSTRING=`? – Wiktor Stribiżew Feb 26 '19 at 23:41
  • Yes I can hardcore `MYSTRING=`. – bli00 Feb 26 '19 at 23:47
  • There [comes a time](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454) when regular expressions are no longer the best solution to a problem... – Luke Joshua Park Feb 27 '19 at 00:06

1 Answers1

1

You may use ReplaceAllStringFunc and use a regex like

(?m)^bar:(?:\n\s{4}.*)+

See the regex demo. It will match a bar block indented with four whitespaces. Then, after a match is obtained, you may use a regular ReplaceAllString on the match.

See the Go demo:

package main

import (
    "fmt"
    "regexp"
)

const sample = `foo:
    blahblah
    MYSTRING=*
bar:
    blah
    blah
    MYSTRING=*`

func main() {
    re := regexp.MustCompile(`(?m)^bar:(?:\n\s{4}.*)+`)
    re_2 := regexp.MustCompile(`(MYSTRING=).*`)
    s := re.ReplaceAllStringFunc(sample, func(m string) string {
                return re_2.ReplaceAllString(m, `${1}stringofmychoice`)
        })
    fmt.Println(s)
}

Here, the second occurrence is changed in the bar block:

foo:
    blahblah
    MYSTRING=*
bar:
    blah
    blah
    MYSTRING=stringofmychoice
Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
  • Sorry, I think I might have worded my question a bit too confusingly. I meant to say that I need the `MYSTRING=*` replaced with `MYSTRING=stringofmychoice` after a **certain** string has appeared, not after it has appeared a certain number of times. In the example above, that **certain** string would be `bar`. I want to find a string, `bar`, and change the `MYSTRING=*` into `MYSTRING=barstring` after it. – bli00 Feb 27 '19 at 00:21
  • @thestateofmay That does not change much. – Wiktor Stribiżew Feb 27 '19 at 00:22
  • Would it be possibly to greedily use the matching between `bar` and `MYSTRING=` instead? I'm trying something like `re := regexp.MustCompile("(bar).(MYSTRING=).*")` but it's not working. – bli00 Feb 27 '19 at 00:43
  • @thestateofmay ``re := regexp.MustCompile("((?s:bar.*?MYSTRING=)).*")``. Not sure what you mean though. If you plan to use it as a 1-regex solution, [watch out](https://regex101.com/r/L3sXFB/1). Since there is no lookahead support, it is difficult to come up with a working and readable regex for this. – Wiktor Stribiżew Feb 27 '19 at 08:54