Search code examples
bashsedansi-c

Using ANSI-C quoting in sed


I have a file named "insert.txt". It can look like this (or uglier):

ASDFG?|??|?\/\HJKL<MNBVCXZQWERTYUIOP
zxvbnmlkjhgfdsaqwertyuiop
123"'`~4567890987654321!@#$%^&*()
@#$%^&*()+_}{":?>

And I want to replace a block of text in a target file (target.txt) which is delimited by "STARTSTACKOVERFLOW" to "STOPSTACKOVERFLOW". (I am simplifying the question a little here, but it's the same).

The bash script I use to do this is:

TARGETFILE=target.txt
SOURCEFILE=insert.txt
SOURCETXT="$(<$SOURCEFILE)"
DELIMTXT=$(printf "%q" "$SOURCETXT")

sed -i -e "/STARTSTACKOVERFLOW/,/STOPSTACKOVERFLOW/cSTARTSTACKOVERFLOW\n\n${DELIMTXT}\n\nSTOPSTACKOVERFLOW\n" $TARGETFILE

The problem is that what is pasted into "target.txt" is actually ANSI-C quoted:

$'ASDFG?|??|?\/\HJKL<MNBVCXZQWERTYUIOP
zxvbnmlkjhgfdsaqwertyuiop
123"'`~4567890987654321!@#$%^&*()
@#$%^&*()+_}{":?>'

Note the $'' added.

The reason is that the printf "%q" is producing this quoting style. I would like to avoid that - though need it because I must escape all the badness in this file.

Is there a better way of doing the above using bash and sed?


Solution

  • POSIX sed has an 'r' command to read from a file. So:

    sed -i -e '/STARTSTACKOVERFLOW/,/STOPSTACKOVERFLOW/r large.txt' target.txt
    

    The only issue is whether the file is read once, or once per line between the start and stop lines. I suspect it is read once per line...and working out how to throw the extra lines away is harder...but maybe:

    sed -i -e '/STOPSTACKOVERFLOW/r large.txt' \
           -e '/STARTSTACKOVERFLOW/,/STOPSTACKOVERFLOW/d' target.txt
    

    Simple demo

    This version removes the start and end markers.

    $ cat data
    sdasas
    adsasdas
    start
    more
    more
    end
    sdasda
    sdasdad
    $ cat replace
    replace1
    replace2
    replace3
    $ sed -e '/^end$/r replace' -e '/start/,/end/d' data
    sdasas
    adsasdas
    replace1
    replace2
    replace3
    sdasda
    sdasdad
    

    Preserving the start and end markers

    $ cat sedfile
    /^end$/{
    a\
    start
    r replace
    a\
    end
    }
    /^start$/,/^end$/d
    $ sed -f sedfile data
    sdasas
    adsasdas
    start
    replace1
    replace2
    replace3
    end
    sdasda
    sdasdad
    $ 
    

    This is fiddlier - I would not try to do it without using a file for the script, but you could do so if you wanted to. It is not a one-liner, though.