bashproperties-file

Bash scripting: update a properties file


propertyOne=1
propertyTwo=a/b
propertyThree=three

How do I change the content of the property file to the following pattern?

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

Solution

  • 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.