Expand environment variables in text

The following alternative has the desired effect without resorting to a library:

  • reads the map of environment variables once at startup
  • on calling expandEnvVars() takes the text with potential placeholders as an argument
  • the method then goes through the map of environment variables, one entry at at time, fetches the entry's key and value
  • and tries to replace any occurrence of ${<key>} in the text with <value>, thereby expanding the placeholders to their current values in the environment
    private static Map<String, String> envMap = System.getenv();        
    public static String expandEnvVars(String text) {        
        for (Entry<String, String> entry : envMap.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            text = text.replaceAll("\\$\\{" + key + "\\}", value);
        }
        return text;
    }

If you don't want to write the code for yourself, the Apache Commons Lang library has a class called StrSubstitutor. It does exactly this.


Based on the accepted answer but without nested pattern replacement. Supports also default value and underscores in env-name: ${env-var[:default]}

  static String substituteEnvVars(String text) {
        StringBuffer sb = new StringBuffer();
        String pattern = "\\$\\{([A-Za-z0-9_]+)(?::([^\\}]*))?\\}";
        Pattern expr = Pattern.compile(pattern);
        Matcher matcher = expr.matcher(text);
        while (matcher.find()) {
            final String varname = matcher.group(1);
            String envValue = System.getenv(varname);
            if (envValue == null) {
                envValue = matcher.group(2);
                if (envValue == null)
                    envValue = "";
            }
            matcher.appendReplacement(sb, envValue);
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

In additon variables are exactly substituted once. Text "${VAR}${X}" with VAR=${X} and X=x will be return "${X}x" not "xx".


You don't want to use matches(). Matches will try to match the entire input string.

Attempts to match the entire region against the pattern.

What you want is while(matcher.find()) {. That will match each instance of your pattern. Check out the documentation for find().

Within each match, group 0 will be the entire matched string (${appdata}) and group 1 will be the appdata part.

Your end result should look something like:

String pattern = "\\$\\{([A-Za-z0-9]+)\\}";
Pattern expr = Pattern.compile(pattern);
Matcher matcher = expr.matcher(text);
while (matcher.find()) {
    String envValue = envMap.get(matcher.group(1).toUpperCase());
    if (envValue == null) {
        envValue = "";
    } else {
        envValue = envValue.replace("\\", "\\\\");
    }
    Pattern subexpr = Pattern.compile(Pattern.quote(matcher.group(0)));
    text = subexpr.matcher(text).replaceAll(envValue);
}

Tags:

Java

Regex