mcdito13
mcdito13

Reputation: 321

sed command to replace data within curly braces

I have a file with some data like this:

upstream fibonacci {        # restservers
        server 10.128.0.3;  # 0
        server 10.128.0.5;  # 1
        server 10.128.0.7;  # 2
        server 10.128.0.8;  # 3
        server 10.128.0.10; # 4
}  

I'm trying to figure out a sed command to search for this entire block, and replace it with a string.

I currently have:

sed -i -e "s/upstream[:space:]fibonacci[:space:]\{.*\}/$2/g" testfile.sh

($2 is the string argument passed to this bash script)

This is my error

sed: -e expression #1, char 46: Invalid content of \{\}

I'm still learning my regexs, any help would be great. Thanks!

Edit: Another option would be to just replace the text between the curly braces

Upvotes: 2

Views: 3240

Answers (4)

wp78de
wp78de

Reputation: 18950

sed is not ideal for multiline matches. I would use perl instead.

use strict;

my $str = 'upstream fibonacci {        # restservers
        server 10.128.0.3;  # 0
        server 10.128.0.5;  # 1
        server 10.128.0.7;  # 2
        server 10.128.0.8;  # 3
        server 10.128.0.10; # 4
}';
my $regex = qr/^upstream\sfibonacci\s{.*}$/msp;
my $subst = '$2';

my $result = $str =~ s/$regex/$subst/rg;

print "The result of the substitution is' $result\n";

For an in-place multi-line regex replace (with backup) you can run this line:

perl -0777 -i.bak -pe "s/\nupstream\sfibonacci\s{.*}\s*\n/abc/smg" test.txt

Upvotes: 0

Paul
Paul

Reputation: 1657

A sed solution might be:

sed -i '/upstream[[:space:]]fibonacci[[:space:]]{/{:t;N;/}/!bt;s/.*/your_replacement_string/}' your_file_name

/upstream[[:space:]]fibonacci[[:space:]]{/

Only upstream fibonacci { being found did the next commands be executed

{/{:t;N;/}/!bt;s/.*/your_replacement_string/}

  1. We first define a branch label :t;
  2. Then we tell sed to append the next line to the current line N
  3. The bt will branch back to the label t, and the N command will be executed.
  4. If } being found, the bt won't jump back.
  5. Then the replace statements s/.*/your_replacement_string/ will reach.

Upvotes: 0

John1024
John1024

Reputation: 113814

Using awk (recommended)

This can be done with sed but awk is a better choice.

For one, sed commands like "s/something/$2/" are very dangerous unless the contents of $2 are sanitized of all sed-active characters.

Let's take this sample file:

$ cat file
before...
upstream fibonacci {        # restservers
        server 10.128.0.3;  # 0
        server 10.128.0.5;  # 1
        server 10.128.0.7;  # 2
        server 10.128.0.8;  # 3
        server 10.128.0.10; # 4
}  
after...

To make the replacement using awk:

$ awk -v x="Replacement"  '/upstream[[:space:]]+fibonacci[[:space:]]+\{/{f=1} !f{print} /}/{print x; f=0}' file
before...
Replacement
after...

How it works:

  • -v x="Replacement"

    This assigns a string to awk variable x. Note that it is safe to use x="$2" because it will not matter if x contains awk-active characters.

  • /upstream[[:space:]]+fibonacci[[:space:]]+\{/{f=1}

    Whenever we encounter our starting line, set variable f to 1 (true)

  • !f{print}

    If f is not true, print the current line.

  • /}/{print x; f=0}

    If the current line contains }, then print x and set f back to zero (false).

Using sed

Let's define a replacement string as shell variable r:

$ r="Replacement"

Here is a sed-command to do the replacement:

$ sed -E ":a; /upstream[[:space:]]+fibonacci[[:space:]]+\{/{ /}/!{N;ba}; s/.*/$r/ }" file
before...
Replacement
after...

How it works:

  • :a

    This defines a label a.

  • /upstream[[:space:]]+fibonacci[[:space:]]+\{/

    This matches on the starting string. For lines that match, the following commands are executed:

  • /}/!{N;ba}; s/.*/$r/

    If the line contains }, then we read in the next line (N) and branch back to label a (ba). If we didn't branch back, we replace all of the pattern space with the value of shell variable r.

If you want, $r can of course be replaced with $2:

sed -E ":a; /upstream[[:space:]]+fibonacci[[:space:]]+\{/{ /}/!{N;ba}; s/.*/$2/ }" file

Note that, if $r (or $2) contains sed-active characters, unexpected things can happen. Specially-crafted values could, for example, cause the code to write to your file system. If you are not confident that this variable is safe, use the awk code instead.

Upvotes: 3

axiac
axiac

Reputation: 72177

By default, sed commands work with one line at a time. In order to ask them to process a set of consecutive rows you have to prefix them with two addresses (not all sed commands accept two addresses).

An address can be a number (a line number), $ (the last line in the input) or a delimited regular expression. A command line with two addresses selects an inclusive range. This range starts with the first pattern space that matches the first address. The end of the range is the next following pattern space that matches the second address.

Enough with the theory, let's write a sed command:

sed -i -e "/^upstream fibonacci/,/}/c\\
$2" testfile.sh

Yes, the command spans two lines, it is not wrapped by mistake or for readability.

The sed command used is c and it expects the replacement string starting on the next line. The sed expression ends with the quotes after $2.

If your shell is bash then you can alternatively insert a new line into the command using the $'\n':

sed -i -e "/^upstream fibonacci/,/}/c\ "$'\n'"$2" testfile.sh

How it works

The sed command is c. Its documentation explains:

c \
text

Replace the selected lines with text, which has each embedded newline preceded by a backslash.

It is preceded by two regular expression addresses: /^upstream fibonacci/ is the start of the range and /}/ is the end of the range. Each range starts with a line that begins with upstream fibonacci and ends with the first row after the start row that contains a closing bracket (}).

The entire range is replaced with the text passed as argument to the c command.

Upvotes: 0

Related Questions