简体   繁体   中英

Replace multiline string with sed

I have a file that's basically an INI/CFG file the looks like this:

[thing-a]
attribute1=foo
attribute2=bar
attribute3=foobar
attribute4=barfoo

[thing-b]
attribute1=dog
attribute3=foofoo
attribute4=castles

[thing-c]
attribute1=foo
attribute4=barfoo

[thing-d]
attribute1=123455
attribute2=dogs
attribute3=biscuits
attribute4=1234

Each 'thing' has a set of attributes that could include all the same ones or a subset there of.

I am trying to write a small bash script that will replace the attributes for 'thing-c' with a predefined block $a1, $a2 & $a3 are generated elsewhere in the wider script:

NEW_BLOCK="[thing-c]
attribute1=${a1}
attribute2=${a2}
attribute3=${a3}"

I can find the right block with sed like this:

THING_BLOCK=$(sed -nr "/^\[thing-c\]/ { :l /^\s*[^#].*/ p; n; /^\[/ q; b l; }" ./myThingFile)

I'm not sure if i've gone down a rabbit hole or what with this and I'm pretty sure there is a better way of doing it.

I'm wanting to do what is:

sed "s/${THING_BLOCK}/${NEW_BLOCK}/"

But I can't quite figure out the multiline aspect to this and I'm not sure what the best route to take is.

Is there a way to do this sort of multiline find and replace with sed (or a better way with bash)

Is there a way to do this sort of multiline find and replace...

Yes there is indeed a better way, albeit using awk :

awk -v blk="$NEW_BLOCK" -v RS= '{ORS = RT} $1 == "[thing-c]" {$0 = blk} 1' file

Using -v RS= we use an empty record separator that splits records in input file on each new line.

Another awk. Store the replacement to file2 and:

$ awk -v RS="" '
NR==FNR {
    b=$0
    next
}
$1~/thing-c/ {
    $0=b
}
{
    print (++c==1?"":ORS) $0
}' file2 file1

Output:

[thing-a]
attribute1=foo
attribute2=bar
attribute3=foobar
attribute4=barfoo

[thing-b]
attribute1=dog
attribute3=foofoo
attribute4=castles

[thing-c]
attribute1=${a1}
attribute2=${a2}
attribute3=${a3}

[thing-d]
attribute1=123455
attribute2=dogs
attribute3=biscuits
attribute4=1234

When you want to use sed (IMHO awk is better here), you must have "nice" data (no special characters that sed will try to handle and [ inside block thing-3 ).
I tested with

read -d '' -r NEW_BLOCK <<END
[thing-c]
attribute1=${a1}
attribute2=${a2}
attribute3=${a3}
END

For my solution I first need to replace newlines in $NEW_BLOCK with the two characters \n .

echo "This is the replacement string: ${NEW_BLOCK//$'\n'/\\n}"

With the "multi-line" option "-z" you can do

sed -rz "s/\[thing-c\][^[]*/${NEW_BLOCK//$'\n'/\\n}\n\n/" myThingFile

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM