6
\$\begingroup\$

The code below will split environment variables from a command line (always appear at the end of the command line). Environment variables are represented by '-E key=value'. I've achieved this like so, but I'm wondering if there's a more elegant way

public class TestSplit {

  public static void main(String... args) {
    String command = "-ps 4 -pe 5 -E opInstallDir=/home/paul -E opWD=/home/paul/remake -E opFam=fam -E opAppli=appli";

    int startPosition = command.indexOf("-E") + 2;

    String envVars = command.substring(startPosition);

    for(String pair: envVars.split("-E")) {
      String[] kv = pair.split("=");
      System.out.println(kv[0] + " " +kv[1]);
    }
  }

}

EDIT Just to clarify these aren't command line arguments for launching the program from the console, they are command line arguments for launching an external program. The details of which I haven't included.

\$\endgroup\$
4
  • \$\begingroup\$ Have you taken a conscious decision not to use the JVM's -D option, or were you unaware of it? \$\endgroup\$ Commented Mar 7, 2014 at 12:27
  • \$\begingroup\$ Sorry I should clarify the application isn't launched with these, so these aren't command line arguments to launch the java application, rather command line arguments to launch an external program. \$\endgroup\$
    – PDStat
    Commented Mar 7, 2014 at 12:29
  • \$\begingroup\$ Paul, when launching an external command, you should use the multi-argument input mechanism available in Java. See the details in the last part of my answer which I have just edited in. \$\endgroup\$
    – rolfl
    Commented Mar 7, 2014 at 13:28
  • \$\begingroup\$ @rolfl thanks, but I have no influence in the launching of the external program, that is already dealt with, I'm merely sitting in the middle and picking up part of the command. \$\endgroup\$
    – PDStat
    Commented Mar 7, 2014 at 13:53

3 Answers 3

4
\$\begingroup\$

Like @palacsint I will recommend an external library. Apache commons-cli is a decent choice. Another choice (my preference) is java gnu-getopt ... I like it because I am familiar with the notations and standards from previous work. It can be a little complicated the first time around otherwise.

On the other hand, I tend not to use an external library unless the code is already going to be relatively complicated....

But, back to your code.

Why do you have everything in a single String? Why is it not part of the String...args ?

The first thing about command-line arguments is that they get complicated very fast. What if the argument was:

String command = "-ps 4 -pe 5 -E opInstallDir=/opt/OSS-EVAL/thiscode -E opWD=/home/paul/remake -E opFam=fam -E opAppli=appli -Edocs='My Documents' -Eparse=key=value";

I have thrown in a few things there.

First up, on our one machine at work, we really do have the directory /opt/OSS-EVAL/ which we use to install/evaluate OSS software/libraries.

The above will break your parsing because it has the -E embedded in the name.

Next up, is 'POSIX-style' commandline arguments can have quoted values, and also values with an = in the value.

So, things I would recommend to you:

Locate the source of your command-line values. It will likely be available as an array, not a single string. Keep the data as an array!

Second, with the array, it is easier to look for stand-alone values that are -E, or, if the input is -Ekey=value then you look for values that start with -E.

Finally, when you split the key/value on the =, limit the split to 2.

String[] kv = pair.split("=", 2);

Which will preserve any of the = tokens inside the value part.

EDIT:

You have suggested in your edit that this is for sending data to an external command.

If you are using Java to initialize the external command, then please, please, please use the version of exec() that takes a command array, or use the ProcessBuilder which allows you to send all the command-line parameters as separate values in an array!!!

\$\endgroup\$
3
\$\begingroup\$
  1. Currently it prints something like this:

     opInstallDir /home/paul 
     opWD /home/paul/remake 
    

    Note that there is a space before the key. You might want to trim that.

  2. I guess the parameters comes from a user. Starting the program with invalid arguments, like this

    final String command = "-E testNoValue -E opInstallDir=/home/paul";
    

    only prints a stacktrace to the console. It could be more user friendly.

    Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
        at TestSplit.main(TestSplit.java:15)
    
  3. If you have so complicated parameters I suggest you Apache Commons CLI.

    It supports

    Java like properties (ie. java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo)

    It's almost the same as you have in the example (except the space between -E and key). It's advantage that it provides better error-handling, it could generate help/usage messages.

    See also: Effective Java, 2nd edition, Item 47: Know and use the libraries (The author mentions only the JDK's built-in libraries but I think the reasoning could be true for other libraries too.)

\$\endgroup\$
1
\$\begingroup\$

@rolfl recommends keeping the arguments as an array, and I agree. I'd like to explain why that advice is important.

When the array is stringified, it is transformed into a "degraded" format. What if, one of the environment variables should contain a literal space? Then you need an escaping/quoting mechanism. Then you need to be able to quote the quotes, too, in case you want to express a literal quote character or backslash. Very soon, you would be implementing a complex parser, and it will be still be more error-prone than passing an array.

Keep your arguments as an array, and your whole problem goes away!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.