Search code examples
bashtext

Bash command to replace text between two strings, multilines


I'm trying to replace a multiline chunk of text between two known tags, inside a file. This has the following structure:

#STARTTAG
some text with numbers 123
and dots . that I want 
to replace .
#ENDTAG

I've been trying different approaches based on what I've seen online including sed, awk, perl, but I can't seem to make it work.

For instance, this is what I've been trying to implement:

sed -i '' 's/STARTTAG.*ENDTAG/alternative text/g' somefile

but it doesn't do anything. I tried adding ' and " to each string (i.e. the tags & replacement text) but it just does nothing. I'm not sure what I'm doing wrong. Note that I would like both the text to be replaced and the alternative texts to be multiline.

Thank you


Solution

  • What I would do:

    sed '/STARTTAG/,/ENDTAG/{s/.*/alternative text/g;q}' file
    

    yields:

    alternative text
    

    The q command is to instruct sed to quit.

    sed is a line stream oriented tool, so it's purpose is not to process multilines easily for cases more complicated.

    If you need to keep the tags, better use awk to not have a convoluted and non readable & maintenable sed command:

    awk '
        BEGIN{p=1}
        $1=="#STARTTAG"{p=0;print;next}
        $1=="#ENDTAG"{print "alternative text";p=1}
        p
    ' file
    

    #STARTTAG
    alternative text
    #ENDTAG
    

    If you need to change the file on the fly, use sponge from more-utils (debian):

    awk '...' file | sponge file
    

    Another proper approach, is to use , then, the regex looks more natural:

    perl -g -pe 's/(#STARTTAG).*?(#ENDTAG)/$1\nalternative text\n$2/s' file
    

    -g require Perl's >= 5.36. If you have lower version, use perl -0777 instead.

    With this Perl's command, you can use -i switch like sed:

    perl -i -g '...' file
    

    LESS='+/^ +-g' perldoc perlrun
    

    -g
    undefines the input record separator ($/) and thus enables the slurp mode. In other words, it causes Perl to read whole files at once, instead of line by line.