Parameters and Arguments passed on the command line are something that we simply cannot seem to get rid of. Even with today’s UI-Centric Operating System and interaction Model, Application still expose a number of features through the use of Command-Line parameters and arguments.
There are a number of relatively complex libraries for dealing with Command Line parameters in any number of languages, most of which aim for the typical Command-line stuff; switches, switches with arguments, etcetera. Many of these are relatively complex; I created a COM Component in VB6 to parse Command Line Parameters and I believe I even created a C# Port to a self-contained file for some of my C# applications.
There is of course a simpler way of creating, parsing, and evaluating the arguments you are provided on the commandline, though many shrink from the idea- it involves simply changing up how you interpret arguments. Rather than switches, you instead simply interpret all your arguments as a set of key/value pairs.
Most Programming language environments will perform the task of actually splitting your command line at spaces. You have to workaround this since it might interfere with your own parsing. Some languages provide a way to directly access the CommandLine your application ran with, as a single string. Others, like Java, require you to simply paste together the arguments you are provided and hope for the best. (Java also has an anomoly when you are trying to pass arguments through a “java -jar” or “javaw -jar” call, too, but I’ll get to that in a moment).
The method I’ve found ideal is to simply have a method that accepts a string and then returns a HashMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
private static HashMap parseParameters(String sourcecmd) { //list of entries, in name=value form. //command lines aren't typically very long so we'll parse it manually. We //also want some special handling for quoted parameters, to allow for spaces. while(sourcecmd.Contains(" ")) sourcecmd = sourcecmd.replace(" "," "); int currpos = 0; StringBuffer ValueRunner = new StringBuffer(""); //current running value. HashMap ResultParams = new HashMap(); // Result value. String AcquiredKey = "", AcquiredValue = ""; boolean inQuote = false; //flag for whether we are in quotations or not. while (currpos < sourcecmd.length()) { char grabchar = sourcecmd.charAt(currpos); if (grabchar == '\"') { inQuote = !inQuote; } else if (inQuote) { //unused block, we don't want to do //any special processing except for with quotes when //we are within a quoted block, instead slurping each //character directly into ValueRunner. ValueRunner.append(grabchar); } else if (grabchar == '=' && AcquiredKey.length() == 0) { //equals is used to separate name/value pairs. //set the AcquireKey to our current value Runner, //and clear it out in preparation to receive the value. AcquiredKey = ValueRunner.toString(); ValueRunner = new StringBuffer(); //clear it out. } else if (grabchar == ' ' && AcquiredKey.length() > 0) { //indicates end of a value. AcquiredValue = ValueRunner.toString(); ValueRunner = new StringBuffer(); //clear that out. //we have both, add this to the result HashMap. //blank them out. ResultParams.put(AcquiredKey, AcquiredValue); AcquiredKey = ""; AcquiredValue = ""; //blank out both values in preparation for the next pair. } else { ValueRunner.append(grabchar); } currpos++; } if (ValueRunner.length() > 0) { AcquiredValue = ValueRunner.toString(); } if (AcquiredValue != "" && AcquiredKey != "") { ResultParams.put(AcquiredKey, AcquiredValue); } return ResultParams; } |
The logic is exceedingly similar to some of the logic that was involved in my BASeParser XP Expression Evaluator. The ubiquitous “inquote” boolean makes a cameo in this Java implementation. the basic idea is that when it finds a quote, it toggles “inquote”; and it will only recognize separators (spaces) and equals signs (used to separate keys from values) when inquote is false. If it finds a space, it will take what it has for a key and the value it’s building and add those to the HashMap it is building. If it finds an equals sign, it will take what it’s currently got in ValueRunner and throw it into the Key. the purpose being to allow the key and name to be enclosed in quotes if they contain spaces, so they don’t get split apart.
An example usage would require the original command line to be rebuilt:
1 2 |
String fullcommand = Join(args, " "); final HashMap GrabParameters = parseParameters(fullcommand); |
This works by simply rebuilding the arguments Java has so helpfully split for us.
HOWEVER: this does have some caveats. The first seems to be that Java removes the quotations around any quotes entries, and it obviously get’s confused by our non-standard arguments. So we need a custom Join method for this task:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static String Join(String[] arguments,String Separator){ StringBuffer sb = new StringBuffer(); for(int i=0;i<arguments .length;i++){ String currarg = arguments[i]; if(currarg.Contains(" ")) sb.append("\"" + currarg + "\""); else sb.append(currarg); if(i<arguments.length-1) sb.append(Separator); } return sb.toString(); } |
OK so maybe the Join I had was a lot simpler and just a standard Join() that didn’t take spaces and add quotes to each one if they were found. I only realized the caveat involving quotes (which explains the massive mystery that took me 3 hours to solve this morning, which I ended up chalking up to a known netbeans bug, which no doubt did not help clarify the problems I was having. Ideally I would be able to get the pure unfiltered and untainted Command Line; (sort of like .NET’s Environment.CommandLine Property) But there doesn’t appear to be a good way to do that in Java, as near as I can tell, which is a shame.
And that’s that: Now you have a method to parse a command line like this:
1 |
java -jar myprogram.jar Username="billy" password="Hunter42" "main weapon"="slarpy Gun" |
Which will give us these pairs:
Key | Value |
---|---|
Username | billy |
password | Hunter42 |
main weapon | slarpy Gun |
Tada! And now you can add helper methods, or encapsulate this functionality into a class and give it those helper methods, for all sorts of different features that manipulate or return given parameters or sets of command parameters. I personally have a “popParameter()” method that takes the hashmap, a key name, and a default value, and will retrieve the existing value of the given key if found and remove it or if not found return the default value.
Have something to say about this post? Comment!