Reputation: 21
I am trying to flag everything inside a color tag and replace it with something else, such as:
I have a [color=blue]dog[/color] and a [color=blue]cat[/color] in my house.
to
I have a [color=blue][b]foobar[/b][/color] and a [color=blue][b]foobar[/b][/color] in my house.
Here is what I've tried:
sample='I have a [color=blue]dog[/color] and a [color=blue]cat[/color] in my house.'
replace='foobar'
sample=$(echo $sample| sed "s/\[color=blue\].*\[\/color\]/\[color=blue\]\[b\]$replace\[\/b\]\[\/color\]/g")
Which gets me:
I have a [color=blue][b]foobar[/b][/color] in my house.
Any idea on how to make sed nongreedy in this case?
Upvotes: 2
Views: 1920
Reputation:
As other have stated you need to use non greedy by reading non matching characters.
Using a carat inside brackets [^ABC]
effectively means not whatever follows.
So using this with the asterix *
will match only up to the next one of that character.
For example
[^[]*
Will match everything up to the next [
bracket
Also everyone is backslash escaping the replacement which is not needed as it cannot print regex.
Anyway here is a command that should work.
sed 's/\(\[color[^]]*\]\)[^[]*\(\[\/color\]\)/\1[b]foobar\[b]\2/g'
Upvotes: 0
Reputation: 20980
sed
will always be greedy. You can use perl
if you strictly want non-greedy variant:
$ echo $test
I have a [color=blue]dog[/color] and a [color=blue]cat[/color] in my house.
$ perl -pne 's/(\[color=[a-zA-Z]*\])(.*?)(\[\/color\])/$1\[b\]foobar\[\/b\]$3/g' <<< "$test"
I have a [color=blue][b]foobar[/b][/color] and a [color=blue][b]foobar[/b][/color] in my house.
I guess, you can interpret most of the regex here, except for the tiny syntax change:
(.*?)
in place of (.*)
dictates that the match is supposed to be non-greedy.
If you skip ?
after .*
, here is the output you must be getting currently:
$ perl -pne 's/(\[color=[a-zA-Z]*\])(.*)(\[\/color\])/$1\[b\]foobar\[\/b\]$3/g' <<< "$test"
I have a [color=blue][b]foobar[/b][/color] in my house.
Upvotes: 0
Reputation: 5298
sed -r 's/(\[color=[a-z]*\])[a-z]*(\[\/color\])/\1[b]foobar[\/b]\2/g' File
or
sed 's/\(\[color=[a-z]*\]\)[a-z]*\(\[\/color\]\)/\1[b]foobar[\/b]\2/g' File
Explanation:
Here, we look for the patterns 1. [color=any small letter sequence]
followed by 2. any small letter sequence
followed by 3. [/color]
and group patterns 1 and 3
using (
and )
. Then we do the substitutions. We keep the 1st and 2nd groups
(using \1
and \2
), but replace the contents between the first and second group with [b]foobar[/b]
.
Upvotes: 0
Reputation: 8402
sed 's#\(\[color=[[:alpha:]]*\]\)[[:alnum:]]*\(\[/color\)#\1[b]foobar[/b]\2#g'
example
echo 'I have a [color=blue]dog[/color] and a [color=blue]cat[/color] in my house.'|sed 's#\(\[color=[[:alpha:]]*\]\)[[:alnum:]]*\(\[/color\)#\1[b]\2#g'
output
I have a [color=blue][b]foobar[/b][/color] and a [color=blue][b]foobar[/b][/color] in my house.
Upvotes: 0
Reputation: 113924
sed
is always greedy. You can work around it by selecting the regex carefully. The example below is identical to yours except that .*
has been replaced with [^[]*
(which means everything except [
):
$ echo $sample| sed "s/\[color=blue\][^[]*\[\/color\]/\[color=blue\]\[b\]$replace\[\/b\]\[\/color\]/g"
I have a [color=blue][b]foobar[/b][/color] and a [color=blue][b]foobar[/b][/color] in my house.
For truly non-greedy regular expressions, try perl or python.
Upvotes: 1
Reputation: 249394
Just replace your .*
with [^[]*
(any character other than left bracket). That is:
"s/\[color=blue\][^[]*\[\/color\]/\[color=blue\]\[b\]$replace\[\/b\]\[\/color\]/g"
Upvotes: 1