TDN
TDN

Reputation: 425

Manipulate multiple lines between 2 delimiter lines (append if not exist else replace)

In linux, I see a lot of programs add things to my rc files between their own start and end delimiter patterns. For example, with conda:

# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/home/tidu/miniconda3/bin/conda' 'shell.zsh' 'hook' 2>/dev/null)"
if [ $? -eq 0 ]; then
    eval "$__conda_setup"
else
    if [ -f "/home/tidu/miniconda3/etc/profile.d/conda.sh" ]; then
        . "/home/tidu/miniconda3/etc/profile.d/conda.sh"
    else
        export PATH="/home/tidu/miniconda3/bin:$PATH"
    fi
fi
unset __conda_setup
# <<< conda initialize <<<

I want to do the same thing for some of my configs.

# dynamic start pattern
dynamic
body
# dynamic end pattern

dynamic means that the content may be generated dynamically, not hard coded in the script.

I tried a lot but is yet to find out a clean way to do this seemingly very common thing. Please show me how to do this with some commandline tools like awk, sed or perl.

Upvotes: 0

Views: 58

Answers (1)

Renaud Pacalet
Renaud Pacalet

Reputation: 29167

With all these utilities it is quite simple to cut the original file in 3 parts:

  • From beginning to start delimiter (excluded).
  • From start to end delimiters (included).
  • From end delimiter (excluded) to the end.

Replacing the middle part is then easy. Example with awk, assuming the text to update is prepared in bash variable str, and awk variables start and end contain the regular expressions that match the start and end delimiter lines:

# foo.awk
$0 ~ start {
    if(n != 0) {
        n = 3
        exit
    }
    n = 1
}
n == 0 || n == 2 {
    print
}
$0 ~ end {
    if(n != 1) {
        n = 3
        exit
    }
    print str
    n = 2
}
n == 1 {
    next
}
END {
    if(n != 0 && n != 2) {
        print "error"
        exit 1
    } else if(n == 0) {
        print str
    }
}
$ start='# dynamic start pattern'
$ end='# dynamic end pattern'
$ str="\
$start
dynamic
body
$end"
$ awk -v str="$str" -v start="^$start\$" -v end="^$end\$" -f foo.awk foo.rc > new.rc

The most complicated part is detecting errors (start and end in reverse order, one start but no end, one end but no start, more than one start or end...) The n awk variable is used to keep track of the current state: before start (n=0), after end (n=2), in between (n=1) or error (n=3). If an error occurs awk exits with status 1. Else, if it exits with status 0, you can replace the old rc file by the new one.

Upvotes: 1

Related Questions