Regarding @sylvian's answer:
- You don't need
.*
for grep since that pattern doesn't enforce anything and grep will return true if it finds a pattern anywhere on a line.
- You can make your
sed
expression DRYer by capturing the key: sed -i 's/^(firmware_version) .*/\1 12.4/'
- If you don't care about position, you can drop the grep entirely:
sed -i '/^firmware_version/d' config.txt; echo 'firmware_version 12.4' >> config.txt
But seriously, awk
is much better at parsing fields and this is a key/value pair in columns:
touch config.txt
awk -i inplace \
-v 'k=firmware_version' \
-v 'v=12.4' \
'$1 == k{f=1;$2=v} 1; ENDFILE {if(!f) print k, v}' config.txt
(note: -i inplace
is not entirely portable, if it doesn't work for you, consider mktemp
or sponge
)
The touch
will create an empty file if it doesn't exist (a problem that is not addressed by any of the answers so far).
The awk statement says:
- If the first field is exactly "firmware_version", set the "found" variable (
f
) to true and set the second field to "12.4".
- Print all lines (
1
) (including modified lines).
- If at the end of the file, the match was never found, print a new line with "firmware_version 12.4"
This avoids regex entirely. Since we're looking at fields, we don't need to be concerned about anchors, we won't match words found in other fields and we won't accidentally match keys like "firmware_version_update".
It's concise and reusable (you can just change the variables k
and v
) and it avoids using 3 different commands with a true/false chain (which can lead to unexpected results).
This also works with empty files, preserves the placement of "firmware_version" (if it exists) and preserves any text after the new value such as a comment.
firmware_version 12.4 # This comment stays put!
See it in action: https://glot.io/snippets/gbrzh99b0p
Really tiny edge-case
Awk detects fields using a repeating regex space delimiter by default so it will change a line with leading whitespace that looks like this:
firmware_version ...
If there's a comment marker like #
before it, that won't matter. This only matters to lines with explicitly whitespace behind the first column.
If for some incredibly insane reason that means something different for your application, then you would have to use an anchor and pattern matching and $1 == k
becomes
BEGIN {p="^"k} $0 ~ p
.
I think @anubhava's answer (also using awk) is a misinterpretation of the problem since "some text" is likely an unknown placeholder (also, it does not handle empty files).
If leading/trailing lines in a block of text are known, then @johnathan-leffler's answer is better. Here's a slightly more legible script:
/some text/,/some more text/{
/^firmware_version /d
/some more text/ifirmware_version 12.4
}