Reputation: 321
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
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
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/}
:t
;sed
to append the next line to the current line N
bt
will branch back to the label t
, and the N
command will be executed.}
being found, the bt
won't jump back.s/.*/your_replacement_string/
will reach.Upvotes: 0
Reputation: 113814
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).
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
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