1

I am reading a property file in Java.

Properties myProp = new Properties();
InputStream in = new FileInputStream(pathOfPropertyFile);
myProp.load(in);
in.close();

The values in the property file have references to Linux shell variables. For example, an entry in the property file might look like:

DATA_PATH=/data/${PROJECT}/${YEAR}${MONTH}${DAY}

I have to execute a shell script from java and so I have ProcessBuilder instance and also the environment variables (envMap as given below):

List<String> command = new ArrayList<String>();
command.add(actualCommand);
command.add(param1);
command.add(param2);
ProcessBuilder processBuilder = new ProcessBuilder(command);
Map<String, String> envMap = processBuilder.environment();

The envMap has the environment variables I require along with over one hundred (> 100) other environment variables which I do not require.

I want to replace the ${USER},${PROJECT},etc., from the property-value string "/home/${USER}/${PROJECT}/data" with actual values from the shell.

I would consider iterating the Map as the last option(as the Map has between 100 and 200 elements to iterate) as it is not an efficient approach.

Please advise some approach that will fetch the environment variable enclosed by braces from the string, so that I can directly use the get() of the Map and replace. Or, any better approaches are most welcome.

Note: The reference offered ( Replace String values with value in Hash Map that made my question to look duplicate) is not the best fit in my case.

Marco99
  • 1,639
  • 1
  • 19
  • 32
  • 1
    You add the list to itself `command.add(command);`? What have you tried to replace the variable names in `myProp` with the values you got in `envMap`? – SubOptimal Jul 05 '17 at 10:18
  • @SubOptimal Thanks for pointing out the typo error, i corrected it. Iterating the Map elements is not a good idea given the number of elements in the Map (between 100 and 200). – Marco99 Jul 05 '17 at 10:46
  • Are you looking for a way to get `"USER"` from `"${USER}"`? I am not sure what exactly is your issue. Why would you need to iterate the map? – Marvin Jul 05 '17 at 11:18
  • Marvin, You are right. I'm looking for a way to get "USER" from "${USER}" and the same in the case of other environment variables. The reference shared by @vefthym iterates the Map. – Marco99 Jul 05 '17 at 11:55
  • @Marco99 ok, I am retracting my close vote then – vefthym Jul 05 '17 at 12:38
  • Why don't you simply pass the variable names enclosed in `${}` and then just let the shell do the expansion? – Klitos Kyriacou Jul 05 '17 at 12:59
  • @KlitosKyriacou The idea is to use the replaced final string in java context itself. – Marco99 Jul 05 '17 at 14:09

2 Answers2

4

If you are open to using an external library, StrSubstitutor from apache-commons will do exactly what you want:

public static void main(String[] args) {
    String input = "DATA_PATH=/data/${PROJECT}/${YEAR}${MONTH}${DAY}";
    Map<String, String> env = new HashMap<>();
    env.put("PROJECT", "myProject");
    env.put("YEAR", "2017");
    env.put("MONTH", "7");
    env.put("DAY", "5");
    env.put("OTHER_VALUE", "someOtherValue");
    System.out.println(StrSubstitutor.replace(input, env));
}

Output:

DATA_PATH=/data/myProject/201775

It also has a method to directly replace system properties without the need for an explicit map.

(for a non-external-library approach see vefthym's answer)

Marvin
  • 13,325
  • 3
  • 51
  • 57
0

I am not sure if it works, but I hope someone can edit to make it work, or at least you get the logic and make it work on your own:

command = command.replaceAll("\\$\\{(.*?)\\}", envMap.get("$1"));

Here, I am assuming that command is a String (not a List) and that all environment variables exist in your Map (otherwise you should check for null and handle this case as you wish).

A bit of an explanation:

this regex is looking for the pattern "${something}" and replaces it with envMap.get("something"). In this example, we use parentheses to mark "something" as a group, which can then be retracted as "$1" (since we have only one group, i.e., only one set of parentheses). The question mark '?' is the non-greedy operator here meaning to stop at the smallest possible regex match (otherwise it would find a singe match for the first "${" until the last "}".

vefthym
  • 7,422
  • 6
  • 32
  • 58
  • 1
    The call to `envMap.get("$1")` would simply pass "$1" unchanged to the `get` method. You probably need to use a Matcher to get the actual string inside `${...}` and _then_ pass that to `get`. – Klitos Kyriacou Jul 05 '17 at 12:58
  • @Marco99 it's not a trivial change. The basic idea is to use a `Matcher` object (see [Oracle's documentation](http://docs.oracle.com/javase/8/docs/api/java/util/regex/Matcher.html) ) using the regular expression pattern shown above as the first argument to `replaceAll`. Or look at the source code for `StrSubstitutor` (from Marvin's answer) to see how it's implemented. – Klitos Kyriacou Jul 05 '17 at 15:15
  • 1
    @Marco99 I've just found [an answer to another question](https://stackoverflow.com/a/43372206/638028) that shows exactly what I was thinking about. – Klitos Kyriacou Jul 06 '17 at 12:58