Search code examples
bashsed

How to replace string in small part of the text after occurence of certain keyword


I have multiple Azure DevOps pipeline files with many stages like this:

...
- stage: 'qa'
  dependsOn:
  - dev
  condition: |
    and
    (
      condition1,
      condition2,
      ...,
      startswith(variables['Build.SourceBranch'], 'refs/heads/development')
    )

# This needs to stay the same
- stage: 'uat'
  dependsOn:
  - dev
  condition: |
    and
    (
      condition1,
      condition2,
      ...,
      startswith(variables['Build.SourceBranch'], 'refs/heads/development')
    )
...

Now we are updating all these files to new deployment strategy and I need to change two things - the dependsOn clause and the source branch refs/heads/development in the condition - but only for the qa stage.

The result should look like this

...
- stage: 'qa'
  dependsOn:
  - build
  condition: |
    and
    (
      condition1,
      condition2,
      ...,
      startswith(variables['Build.SourceBranch'], 'refs/heads/main')
    )

# This needs to stay the same
- stage: 'uat'
  dependsOn:
  - dev
  condition: |
    and
    (
      condition1,
      condition2,
      ...,
      startswith(variables['Build.SourceBranch'], 'refs/heads/development')
    )
...

I am able to do the first part quite easily - I know there is always just one item in that array. So this will work sed "/stage: 'qa'/{ n; n; s/dev/build/g }" pipeline.yaml

But the second part is harder, there can be multiple conditions and I don't exactly know how many lines to skip.

Is there any way how to tell sed to replace a string only in part of the file, lets say from 4 to 10 lines below occurrence of the stage: 'qa' string?


Solution

  • For structured data, use structure-aware processors, not text processors that can only manipulate the textual representation without seeing/understanding the underlying logic).

    Although the pipeline is yaml, that condition is not yaml so I would still need some text edit tool.

    You could use the YAML processor mikefarah/yq, which also has text-processing capabilities (applicable to the condition):

    yq 'map(select(.stage == "qa").condition |= sub("refs/heads/development"; "refs/heads/main"))'
    
    - stage: 'qa'
      dependsOn:
        - build
      condition: |
        and
        (
          condition1,
          condition2,
          ...,
          startswith(variables['Build.SourceBranch'], 'refs/heads/main')
        )
    
    # This needs to stay the same
    - stage: 'uat'
      dependsOn:
        - dev
      condition: |
        and
        (
          condition1,
          condition2,
          ...,
          startswith(variables['Build.SourceBranch'], 'refs/heads/development')
        )