Mooby The Golden Sock
Mooby The Golden Sock

Reputation: 21

sed replace between two strings wildcard

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

Answers (6)

user3442743
user3442743

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

anishsane
anishsane

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

Arjun Mathew Dan
Arjun Mathew Dan

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

repzero
repzero

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

John1024
John1024

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

John Zwinck
John Zwinck

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

Related Questions