Home > Software design >  Sed command to change a string at only desired place
Sed command to change a string at only desired place

Time:03-29

I want to replace a string from a file using sed in bash script, but that string is present at multiple places in that file.

Is there any way to replace the string using a WHERE clause so I can replace the string only where I want?

Using a line number won't work because I need a script that is more flexible than that allows. Here what I'm trying to do.

I stored the desired piece of code in a variable. Can I use that variable in a sed command? For example,

sed -i "s/condition: succeeded('Fair_PreProd')/condition: succeeded('Fair_UAT')/g" $folder_path/$file_name

Here is the original file:

-stage: Moto_Dev
dependsOn: Build
condition: and(succeeded(), eq(variables.isDevelop, true))

- stage: Unity_Dev
  dependsOn: Build
  condition: and(succeeded(), eq(variables.isUnityDevelop, true))

- stage: QA
  dependsOn: Dev
  condition: succeeded('Dev')

- stage: UAT
  dependsOn: Build
  condition: and(succeeded(), eq(variables.isStaging, true))

There are 3 places where dependsOn: Build is present. I want to replace only the one in the -stage:MotoDev section. How can I do that?

CodePudding user response:

Suggesting an awk solution that reads bash variables.

filter : An awk RegExp filtering correct lines.

target : An awk RegExp to identify the string to be replaced

replacement: A string not RegExp to replace target RegExp.

With provided example:

awk '$0~filter && $0~target{gsub(target,replacement)}1' filter="block4" target="a=c" replacement="this = that" input.file
# same command, less readable, but shorter:
awk '$0~f && $0~t{gsub(t,r)}1' f="block4" t="a=c" r="this = that" input.file

Advantages of this approach:

  • More flexible than sed.
  • Generic: can add more filters and filtering logic.

Disadvantages of this approach:

  • Cannot do in-place replacement on the input file. Like sed -i
  • Therefore need to specify all files explicitly one by one.

sample output:

awk '$0~filter && $0~target{gsub(target,replacement)}1' filter="block4" target="a=c" replacement="this = that" input.1.txt 
block1{ a=c } 
block2{ v=c }
block3{ w=c }
block4{ this = that }
block5{ a=c }

CodePudding user response:

Is there any way to replace the string using a WHERE clause so I can replace the string only where I want?

sed does not have SQL-style WHERE clauses, but commands can have "addresses" that define subsets of input lines to operate upon. These can take several forms. Regular expressions are perhaps the most common, but there are also line numbers, and a couple of special forms. You can also have inclusive ranges built from simple addresses. An address range would be a reasonably good way to address the problem you present.

For example,

sed -i '/^\s*-\s*stage:\s*Moto_Dev/,/^\s*-/ s/dependsOn: Build/dependsOn: Test/' input

Explanation:

  • The -i command-line flag tells sed to work "in-place", which really means that it will replace the original file with one containing sed's output.

  • The /^\s*-\s*stage:\s*Moto_Dev/,/^\s*-/ is a range address, consisting of a regex for the range start (/^\s*-\s*stage:\s*MotoDev/) and one for the range end (/^\s*-/).

    • /^\s*-\s*stage:\s*Moto_Dev/ matches the beginning of the section in which you want the change to be made, with some flexibility around the exact amount of whitespace at certain positions. For brevity and clarity, it uses \s to represent a single space or tab character. That is a GNU extension, but if you cannot depend on GNU sed then there are other ways to express the same thing.
    • /^\s*-/ matches the beginning of the next section, as you have presented the input. It could be made more specific if it were necessary to be more selective. The range includes its endpoints, but that does not appear to be a problem for the task at hand.
  • There is only one such range in the input presented, and that range contains the line you want to modify. The specified substitution, s/dependsOn: Build/dependsOn: Test/, is performed on each line in the range, but only the one contains a match to be replaced. All others in the range will be unaffected.

  • No commands at all are specified for lines outside the range, so they too will be unaffected.


You also asked,

I stored the desired piece of code in a variable. Can I use that variable in a sed command? For example,

sed -i "s/condition: succeeded('Fair_PreProd')/condition: succeeded('Fair_UAT')/g" $folder_path/$file_name

sed does not expand shell-style parameter references, but you don't need it to do. The variable references in that command are expanded by the shell itself, before it executes the resulting command, so

  • yes, you may use them, and
  • it's not a question of using shell variables with sed in particular.
  • Related