propertyOne=1
propertyTwo=a/b
propertyThree=three
How do I change the content of the property file to the following pattern?
propertyThree will add a string at the end
propertyOne=apple/1
propertyTwo=a/and/b
propertyThree=three/end
I tried using sed -i -e
but I am only successful if I hard-code the changes for every line; any suggestions for improving the code?
sed -i -e '/propertyTwo=/ s=.*/=one/2/two' path/to/file
In this case, a pure Bash solution offers both flexibility and robustness (but see further below for a faster awk
solution).
While Bash solutions that read files line by line are generally slow, this probably won't be a concern with properties files, which tend to be small.
#!/usr/bin/env bash
while IFS='=' read -r prop val; do
case $prop in
propertyOne)
val="apple/$val"
;;
propertyTwo)
val="${val/\///and/}"
;;
propertyThree)
val="$val/end"
;;
esac
printf '%s\n' "$prop=$val"
done < file > file.tmp && mv file.tmp file
The Bash builtin read
conveniently offers rest-of-the-line logic:
by only specifying 2 variables in IFS='-' read -r prop value
, the 2nd variable value
receives everything after the first =
, whatever it is, even if it contains additional =
instances.
< file > file.tmp && mv file.tmp file
is a common idiom for (loosely speaking) in-place updating of a file. Technically, the modified content is written to a temp. file, and that temp. file then replaces the original.
Note:
* This indirect way of updating is needed, because the shell doesn't support reading from and outputting to the same file in the same command.
* This simple approach can be problematic, in that if the input file was a symlink, it is replaced with a regular file, the new file's permissions may be different, ...
awk
, as demonstrated in karakfa's answer, is certainly the faster choice, but it comes with a caveat - which may or may not be a problem for you:
Conceptually, a properties file is not strictly field-based, because a property value may contain value-internal =
instances.
If you split the input into fields by =
, then generic value handling can be problematic, because you won't have a single variable referring to the value as a whole.
A quick example: Say you have an input line foo=bar=baz
, and you want to append string @
to the existing value, bar=baz
, without having to know ahead of time whether the existing value happens to have embedded =
chars.
If you blindly use $2 = $2 "@"
for appending, the resulting value will be just bar@
- in other words: you've lost data.
Solving this problem requires a little more work; here's an awk
solution adapted from karakfa's, which provides the whole value in single variable val
:
awk -F= '
# Capture the entire value (everything to the right of "=") in variable "val".
{ val= $0; sub("^[^=]+=", "", val) }
$1 == "propertyOne" { val = "apple/" val }
$1 == "propertyTwo" { sub(/\//, "/and/", val) }
$1 == "propertyThree" { val = val "/end" }
{ print $1 "=" val }
' file > file.tmp && mv file.tmp file
Note: If you use GNU awk
and the version number is >= 4.1, you can use -i inplace
instead of > file.tmp && mv file.tmp file
to update the input file in-place (loosely speaking). Beside being more convenient than the latter approach, -i inplace
also preserves the original file's permissions, but the basic approach is the same: the file is replaced, which bears the risk of replacing a symlink with a regular file.
sed
is not a good choice, because it's hard to limit substitutions to part of a line in a generic manner.