1. Using AWK
Here is a possible solution to the more general problem of adding a string to a labeled block of text (i.e. the lines of text that follow a specific label in a file and precede the next label or the ond-of-file) only if string is not present yet.
A simple approach is to walk the file backwards, keep track of the last time we encountered string, print it every time we encounter a specific label (just before printing the label itself), but only if we haven't seen it (string) yet, and forget about having encountered it (string) any time we come across a label. And, of course, reverse the result as the last step.
tac file | awk '
/^force_https_protocol=PROTOCOL_TLSv1_2$/ {
seen = 1
}
/^\[security\]$/ {
if ( ! seen ) {
print "force_https_protocol=PROTOCOL_TLSv1_2"
}
}
/^\[[^]]*\]$/ {
seen = 0
}
1' | tac
tac
, included in GNU Coreutils, concatenates and reverses files line by line. How to reverse a file with other/standard tools is covered in several Q/As on U&L (for instance, How does the command sed '1!G;h;$!d' reverse the contents of a file? and Reverse grepping).
This will not insert force_https_protocol=PROTOCOL_TLSv1_2
after the first [security]
in the following text snippet:
[security]
keysdir=/var/lib/ambari-agent/keys
force_https_protocol=PROTOCOL_TLSv1_2
passphrase_env_var_name=AMBARI_PASSPHRASE
[security]
...
To simply insert string after every occurrence of a label unless string is already there, the same approach may lead to:
tac file | awk '
/^force_https_protocol=PROTOCOL_TLSv1_2$/ {
seen = NR
}
/^\[security\]$/ {
if ( ( NR == 1 ) || ! ( NR == seen + 1 ) ) {
print "force_https_protocol=PROTOCOL_TLSv1_2"
}
}
1' | tac
(This will insert force_https_protocol=PROTOCOL_TLSv1_2
after [security]
in the above text sample).
2. Using sed
With sed
you can use a variation of the N;P;D;
cycle (which is abundantly covered in other Q/As on U&L; for instance: How can I use sed to replace a multi-line string? or How to edit next line after pattern using sed?).
This will insert the line force_https_protocol=PROTOCOL_TLSv1_2
after a line composed solely of [security]
only if the former is not already there.
sed '
$! {
N
P
/^\[security\]\n/ {
/\nforce_https_protocol=PROTOCOL_TLSv1_2$/ b b
i\
force_https_protocol=PROTOCOL_TLSv1_2
}
:b
D
}
s/^\[security\]$/&\
force_https_protocol=PROTOCOL_TLSv1_2/'
This script:
- for each line, except for the last one (
$!
), appends a newline followed by the next line to the pattern space (N
) (which already contains the current line);
- prints the first line in the pattern space (
P
); if that line is [security]
...
- ...and the next line is
force_https_protocol=PROTOCOL_TLSv1_2
, just deletes the first line from the pattern space and starts a new cycle (b b
, :b D
);
- otherwise, prints
force_https_protocol=PROTOCOL_TLSv1_2
to standard output (i\
) before deleting the first line from the pattern space and starting a new cycle (D
);
- if the last line is
[security]
, replaces it with itself followed by a newline and force_https_protocol=PROTOCOL_TLSv1_2
and prints the result (by means of the implied p
).
force_https_protocol=PROTOCOL_TLSv1_2
, when already present, will only appear on the lines that immediately follow[security]
? In other words, can we assume that instances offorce_https_protocol=PROTOCOL_TLSv1_2
that appear elsewhere after[security]
(if any) are not a concern to you? – fra-san Mar 15 '20 at 20:26