How to insert text after the last configuration line

replacetext processing

I have to insert a line into a configuration files automatically, but with the added caveat that it should be inserted before a multi-line footer comment and any preceding empty or whitespace-only lines if the footer exists. That is, the new line should be inserted right after the last configuration line, making for a single-line diff from the original file. In pseudo-code:

  1. Go to the end of the file.
  2. Go backwards to the first (that is, last in the file) configuration line (that is, a line which is not empty, whitespace-only, comment only or whitespace followed by a comment).
  3. Insert text after the current line.

Extended regular expression for configuration line: ^\s*[^[:space:]#]

Any common *nix tools such as sed, awk, ed or ex should work.


Possible solutions and their problems:

  • Use tac twice to make this into a forward-searching problem rather than backward-searching. This means I'll have to store the result in a temporary file then replace the original, rather than doing this in a single command.
  • Use sed -i with the reversal trick. This means storing the entire file in memory.
  • ex -c '1' -c '?^\s*[^[:space:]#]?' -c $'a\nmy new line\n.' -c 'wq' /path, which I'm also given to understand stores the full file in memory.

Is there a solution which circumvents both of these issues?

Example starting file:

# Universe configuration
#

pi = 3 # A good #
e = mc**2 # To within a hair

[cut 200 trillion lines]

# 
# END
#

Example input:

sol { mass = 42, start = 9.2 }

Expected output:

# Universe configuration
#

pi = 3 # A good #
e = mc**2 # To within a hair

[cut 200 trillion lines]
sol { mass = 42, start = 9.2 }

# 
# END
#

General-purpose function based on @StephaneChazelas's solution

Best Answer

You could do something along these lines:

file=/some/file
newtext='sol { mass = 42, start = 9.2 }'
tac -- "$file" | 
  NEWTEXT=$newtext awk -v size="$(wc -c < "$file")" '
    $1 ~ /^[^#]/ {
      system("dd bs=1 seek=" size - length(footer) " conv=notrunc if=/dev/null")
      printf "%s\n%s", ENVIRON["NEWTEXT"], footer
      exit
    }
    {footer=$0 "\n" footer}' 1<> "$file"

That overwrites the file in place and only stores the footer in memory. It needs the non-standard GNU tac command. The file has to be a regular text file.

Related Question