Reading java .properties file from bash

Viewed 34659

I am thinking of using sed for reading .properties file, but was wondering if there is a smarter way to do that from bash script?

13 Answers

The solutions mentioned above will work for the basics. I don't think they cover multi-line values though. Here is an awk program that will parse Java properties from stdin and produce shell environment variables to stdout:

BEGIN {
    FS="=";
    print "# BEGIN";
    n="";
    v="";
    c=0; # Not a line continuation.
}
/^\#/ { # The line is a comment.  Breaks line continuation.
    c=0;
    next;
}
/\\$/ && (c==0) && (NF>=2) { # Name value pair with a line continuation...
    e=index($0,"=");
    n=substr($0,1,e-1);
    v=substr($0,e+1,length($0) - e - 1);    # Trim off the backslash.
    c=1;                                    # Line continuation mode.
    next;
}
/^[^\\]+\\$/ && (c==1) { # Line continuation.  Accumulate the value.
    v= "" v substr($0,1,length($0)-1);
    next;
}
((c==1) || (NF>=2)) && !/^[^\\]+\\$/ { # End of line continuation, or a single line name/value pair
    if (c==0) {  # Single line name/value pair
        e=index($0,"=");
        n=substr($0,1,e-1);
        v=substr($0,e+1,length($0) - e);
    } else { # Line continuation mode - last line of the value.
        c=0; # Turn off line continuation mode.
        v= "" v $0;
    }
    # Make sure the name is a legal shell variable name
    gsub(/[^A-Za-z0-9_]/,"_",n);
    # Remove newlines from the value.
    gsub(/[\n\r]/,"",v);
    print n "=\"" v "\"";
    n = "";
    v = "";
}
END {
    print "# END";
}

As you can see, multi-line values make things more complex. To see the values of the properties in shell, just source in the output:

cat myproperties.properties | awk -f readproperties.awk > temp.sh
source temp.sh

The variables will have '_' in the place of '.', so the property some.property will be some_property in shell.

If you have ANT properties files that have property interpolation (e.g. '${foo.bar}') then I recommend using Groovy with AntBuilder.

Here is my wiki page on this very topic.

This is a solution that properly parses quotes and terminates at a space when not given quotes. It is safe: no eval is used.

I use this code in my .bashrc and .zshrc for importing variables from shell scripts:

# Usage: _getvar VARIABLE_NAME [sourcefile...]
# Echos the value that would be assigned to VARIABLE_NAME
_getvar() {
  local VAR="$1"
  shift
  awk -v Q="'" -v QQ='"' -v VAR="$VAR" '
    function loc(text) { return index($0, text) }
    function unquote(d) { $0 = substr($0, eq+2) d; print substr($0, 1, loc(d)-1) }
    { sub(/^[ \t]+/, ""); eq = loc("=") }
    substr($0, 1, eq-1) != VAR { next }  # assignment is not for VAR: skip
    loc("=" QQ) == eq { unquote(QQ); exit }
    loc("="  Q) == eq { unquote( Q); exit }
    { print substr($1, eq + 1); exit }
  ' "$@"
}

This saves the desired variable name and then shifts the argument array so the rest can be passed as files to awk.

Because it's so hard to call shell variables and refer to quote characters inside awk, I'm defining them as awk variables on the command line. Q is a single quote (apostrophe) character, QQ is a double quote, and VAR is that first argument we saved earlier.

For further convenience, there are two helper functions. The first returns the location of the given text in the current line, and the second prints the content between the first two quotes in the line using quote character d (for "delimiter"). There's a stray d concatenated to the first substr as a safety against multi-line strings (see "Caveats" below).

While I wrote the code for POSIX shell syntax parsing, that appears to only differ from your format by whether there is white space around the asignment. You can add that functionality to the above code by adding sub(/[ \t]*=[ \t]*/, "="); before the sub(…) on awk's line 4 (note: line 1 is blank).

The fourth line strips off leading white space and saves the location of the first equals sign. Please verify that your awk supports \t as tab, this is not guaranteed on ancient UNIX systems.

The substr line compares the text before the equals sign to VAR. If that doesn't match, the line is assigning a different variable, so we skip it and move to the next line.

Now we know we've got the requested variable assignment, so it's just a matter of unraveling the quotes. We do this by searching for the first location of =" (line 6) or =' (line 7) or no quotes (line 8). Each of those lines prints the assigned value.

Caveats: If there is an escaped quote character, we'll return a value truncated to it. Detecting this is a bit nontrivial and I decided not to implement it. There's also a problem of multi-line quotes, which get truncated at the first line break (this is the purpose of the "stray d" mentioned above). Most solutions on this page suffer from these issues.

In order to let Java do the tricky parsing, here's a solution using jrunscript to print the keys and values in a bash read-friendy (key, tab character, value, null character) way:

#!/usr/bin/env bash
jrunscript -e '
        p = new java.util.Properties();
        p.load(java.lang.System.in);
        p.forEach(function(k,v) { out.format("%s\t%s\000", k, v); });
    ' < /tmp/test.properties \
| while IFS=$'\t' read -d $'\0' -r key value; do
    key=${key//./_}
    printf -v "$key" %s "$value"
    printf '=> %s = "%s"\n' "$key" "$value"
done

I found printf -v in this answer by @david-foerster.

To quote jrunscript: Warning: Nashorn engine is planned to be removed from a future JDK release

Related