Parsing arguments to a Java command line program
Asked Answered
H

10

67

What if I wanted to parse this:

java MyProgram -r opt1 -S opt2 arg1 arg2 arg3 arg4 --test -A opt3

And the result I want in my program is:

regular Java args[]  of size=4
org.apache.commons.cli.Options[]  of size=3
org.apache.commons.cli.Options[] #2 of size=1

I would prefer to use Apache Commons CLI, but the documentation is a little unclear about the case I present above. Specifically, the documentation doesn't tell you how to handle options of the 3rd type I specify below:

1. options with a "-" char
2. options with a "--" char
3. options without any marker, or "bare args"

I wish that Apache Commons CLI would work but STILL be able to pass regular args to the program if those args didn't have a option prefix. Maybe it does but the documentation doesnt say so as I read through it...

Hygrothermograph answered 7/9, 2011 at 23:43 Comment(1)
Possible duplicate of How to parse command line arguments in Java?Mafalda
H
35

You could just do it manually.

NB: might be better to use a HashMap instead of an inner class for the opts.

/** convenient "-flag opt" combination */
private class Option {
     String flag, opt;
     public Option(String flag, String opt) { this.flag = flag; this.opt = opt; }
}

static public void main(String[] args) {
    List<String> argsList = new ArrayList<String>();  
    List<Option> optsList = new ArrayList<Option>();
    List<String> doubleOptsList = new ArrayList<String>();

    for (int i = 0; i < args.length; i++) {
        switch (args[i].charAt(0)) {
        case '-':
            if (args[i].length < 2)
                throw new IllegalArgumentException("Not a valid argument: "+args[i]);
            if (args[i].charAt(1) == '-') {
                if (args[i].length < 3)
                    throw new IllegalArgumentException("Not a valid argument: "+args[i]);
                // --opt
                doubleOptsList.add(args[i].substring(2, args[i].length));
            } else {
                if (args.length-1 == i)
                    throw new IllegalArgumentException("Expected arg after: "+args[i]);
                // -opt
                optsList.add(new Option(args[i], args[i+1]));
                i++;
            }
            break;
        default:
            // arg
            argsList.add(args[i]);
            break;
        }
    }
    // etc
}
Hydrocele answered 7/9, 2011 at 23:50 Comment(5)
You should check the length of args[i]!Elkin
I was just typing that in! You could also use a hash table instead of a switch statement if you'd like to be fancy.Mumford
What do you mean by "-flag opt combination"? Do you mean a dash character followed by any number of chars?Hygrothermograph
Ok, ill accept this as the answer although I think optsLists should be converted to HashMap .Hygrothermograph
In for loop you need to initialize the iterator (int i = 0;), otherwise compiler yells at this (Java 7).Irksome
R
81

Use the Apache Commons CLI library commandline.getArgs() to get arg1, arg2, arg3, and arg4. Here is some code:



    import org.apache.commons.cli.CommandLine;
    import org.apache.commons.cli.Option;
    import org.apache.commons.cli.Options;
    import org.apache.commons.cli.Option.Builder;
    import org.apache.commons.cli.CommandLineParser;
    import org.apache.commons.cli.DefaultParser;
    import org.apache.commons.cli.ParseException;

    public static void main(String[] parameters)
    {
        CommandLine commandLine;
        Option option_A = Option.builder("A")
            .required(true)
            .desc("The A option")
            .longOpt("opt3")
            .build();
        Option option_r = Option.builder("r")
            .required(true)
            .desc("The r option")
            .longOpt("opt1")
            .build();
        Option option_S = Option.builder("S")
            .required(true)
            .desc("The S option")
            .longOpt("opt2")
            .build();
        Option option_test = Option.builder()
            .required(true)
            .desc("The test option")
            .longOpt("test")
            .build();
        Options options = new Options();
        CommandLineParser parser = new DefaultParser();

        String[] testArgs =
        { "-r", "opt1", "-S", "opt2", "arg1", "arg2",
          "arg3", "arg4", "--test", "-A", "opt3", };

        options.addOption(option_A);
        options.addOption(option_r);
        options.addOption(option_S);
        options.addOption(option_test);

        try
        {
            commandLine = parser.parse(options, testArgs);

            if (commandLine.hasOption("A"))
            {
                System.out.print("Option A is present.  The value is: ");
                System.out.println(commandLine.getOptionValue("A"));
            }

            if (commandLine.hasOption("r"))
            {
                System.out.print("Option r is present.  The value is: ");
                System.out.println(commandLine.getOptionValue("r"));
            }

            if (commandLine.hasOption("S"))
            {
                System.out.print("Option S is present.  The value is: ");
                System.out.println(commandLine.getOptionValue("S"));
            }

            if (commandLine.hasOption("test"))
            {
                System.out.println("Option test is present.  This is a flag option.");
            }

            {
                String[] remainder = commandLine.getArgs();
                System.out.print("Remaining arguments: ");
                for (String argument : remainder)
                {
                    System.out.print(argument);
                    System.out.print(" ");
                }

                System.out.println();
            }

        }
        catch (ParseException exception)
        {
            System.out.print("Parse error: ");
            System.out.println(exception.getMessage());
        }
    }

Rockwell answered 8/9, 2011 at 0:41 Comment(2)
some of the approaches in this answer are no longer supported. examples for current CLI version: commons.apache.org/proper/commons-cli/usage.htmlLlama
A newer implementation is posted here.Enamor
H
35

You could just do it manually.

NB: might be better to use a HashMap instead of an inner class for the opts.

/** convenient "-flag opt" combination */
private class Option {
     String flag, opt;
     public Option(String flag, String opt) { this.flag = flag; this.opt = opt; }
}

static public void main(String[] args) {
    List<String> argsList = new ArrayList<String>();  
    List<Option> optsList = new ArrayList<Option>();
    List<String> doubleOptsList = new ArrayList<String>();

    for (int i = 0; i < args.length; i++) {
        switch (args[i].charAt(0)) {
        case '-':
            if (args[i].length < 2)
                throw new IllegalArgumentException("Not a valid argument: "+args[i]);
            if (args[i].charAt(1) == '-') {
                if (args[i].length < 3)
                    throw new IllegalArgumentException("Not a valid argument: "+args[i]);
                // --opt
                doubleOptsList.add(args[i].substring(2, args[i].length));
            } else {
                if (args.length-1 == i)
                    throw new IllegalArgumentException("Expected arg after: "+args[i]);
                // -opt
                optsList.add(new Option(args[i], args[i+1]));
                i++;
            }
            break;
        default:
            // arg
            argsList.add(args[i]);
            break;
        }
    }
    // etc
}
Hydrocele answered 7/9, 2011 at 23:50 Comment(5)
You should check the length of args[i]!Elkin
I was just typing that in! You could also use a hash table instead of a switch statement if you'd like to be fancy.Mumford
What do you mean by "-flag opt combination"? Do you mean a dash character followed by any number of chars?Hygrothermograph
Ok, ill accept this as the answer although I think optsLists should be converted to HashMap .Hygrothermograph
In for loop you need to initialize the iterator (int i = 0;), otherwise compiler yells at this (Java 7).Irksome
T
20

I like this one. Simple, and you can have more than one parameter for each argument:

final Map<String, List<String>> params = new HashMap<>();

List<String> options = null;
for (int i = 0; i < args.length; i++) {
    final String a = args[i];

    if (a.charAt(0) == '-') {
        if (a.length() < 2) {
            System.err.println("Error at argument " + a);
            return;
        }

        options = new ArrayList<>();
        params.put(a.substring(1), options);
    }
    else if (options != null) {
        options.add(a);
    }
    else {
        System.err.println("Illegal parameter usage");
        return;
    }
}

For example:

-arg1 1 2 --arg2 3 4

System.out.print(params.get("arg1").get(0)); // 1
System.out.print(params.get("arg1").get(1)); // 2
System.out.print(params.get("-arg2").get(0)); // 3
System.out.print(params.get("-arg2").get(1)); // 4
Tatary answered 15/10, 2014 at 7:11 Comment(2)
I'm surprised there's no library out there that does it this elegant way. All the others compromise your code in one way or another (redundant code, mutable state, additional constructor calls).Hypercatalectic
Enhancement request - can you modify this answer to accept an equals delimiter? :) I'd guess myself but rather you do it.Hypercatalectic
A
15

I realize that the question mentions a preference for Commons CLI, but I guess that when this question was asked, there was not much choice in terms of Java command line parsing libraries. But nine years later, in 2020, would you not rather write code like the below?

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import java.io.File;
import java.util.List;
import java.util.concurrent.Callable;

@Command(name = "myprogram", mixinStandardHelpOptions = true,
  description = "Does something useful.", version = "1.0")
public class MyProgram implements Callable<Integer> {

    @Option(names = "-r", description = "The r option") String rValue;
    @Option(names = "-S", description = "The S option") String sValue;
    @Option(names = "-A", description = "The A file") File aFile;
    @Option(names = "--test", description = "The test option") boolean test;
    @Parameters(description = "Positional params") List<String> positional;

    @Override
    public Integer call() {
        System.out.printf("-r=%s%n", rValue);
        System.out.printf("-S=%s%n", sValue);
        System.out.printf("-A=%s%n", aFile);
        System.out.printf("--test=%s%n", test);
        System.out.printf("positionals=%s%n", positional);
        return 0;
    }

    public static void main(String... args) {
        System.exit(new CommandLine(new MyProgram()).execute(args));
    }
}

Execute by running the command in the question:

java MyProgram -r opt1 -S opt2 arg1 arg2 arg3 arg4 --test -A opt3

What I like about this code is that it is:

  • compact - no boilerplate
  • declarative - using annotations instead of a builder API
  • strongly typed - annotated fields can be any type, not just String
  • no duplication - option declaration and getting parse result are together in the annotated field
  • clear - the annotations express the intention better than imperative code
  • separation of concerns - the business logic in the call method is free of parsing-related logic
  • convenient - one line of code in main wires up the parser and runs the business logic in the Callable
  • powerful - automatic usage and version help with the built-in --help and --version options
  • user-friendly - usage help message uses colors to contrast important elements like option names from the rest of the usage help to reduce the cognitive load on the user

The above functionality is only part of what you get when you use the picocli (https://picocli.info) library.

Now, bear in mind that I am totally, completely, and utterly biased, being the author of picocli. :-) But I do believe that in 2020 we have better alternatives for building a command line apps than Commons CLI.

Archenteron answered 10/3, 2020 at 10:35 Comment(0)
P
9

Here is @DwB solution upgraded to Commons CLI 1.3.1 compliance (replaced deprecated components OptionBuilder and GnuParser). The Apache documentation uses examples that in real life have unmarked/bare arguments but ignores them. Thanks @DwB for showing how it works.

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

public static void main(String[] parameters) {
    CommandLine commandLine;
    Option option_A = Option.builder("A").argName("opt3").hasArg().desc("The A option").build();
    Option option_r = Option.builder("r").argName("opt1").hasArg().desc("The r option").build();
    Option option_S = Option.builder("S").argName("opt2").hasArg().desc("The S option").build();
    Option option_test = Option.builder().longOpt("test").desc("The test option").build();
    Options options = new Options();
    CommandLineParser parser = new DefaultParser();

    options.addOption(option_A);
    options.addOption(option_r);
    options.addOption(option_S);
    options.addOption(option_test);

    String header = "               [<arg1> [<arg2> [<arg3> ...\n       Options, flags and arguments may be in any order";
    String footer = "This is DwB's solution brought to Commons CLI 1.3.1 compliance (deprecated methods replaced)";
    HelpFormatter formatter = new HelpFormatter();
    formatter.printHelp("CLIsample", header, options, footer, true);    

    String[] testArgs =
            { "-r", "opt1", "-S", "opt2", "arg1", "arg2",
                    "arg3", "arg4", "--test", "-A", "opt3", };

    try
    {
        commandLine = parser.parse(options, testArgs);

        if (commandLine.hasOption("A"))
        {
            System.out.print("Option A is present.  The value is: ");
            System.out.println(commandLine.getOptionValue("A"));
        }

        if (commandLine.hasOption("r"))
        {
            System.out.print("Option r is present.  The value is: ");
            System.out.println(commandLine.getOptionValue("r"));
        }

        if (commandLine.hasOption("S"))
        {
            System.out.print("Option S is present.  The value is: ");
            System.out.println(commandLine.getOptionValue("S"));
        }

        if (commandLine.hasOption("test"))
        {
            System.out.println("Option test is present.  This is a flag option.");
        }

        {
            String[] remainder = commandLine.getArgs();
            System.out.print("Remaining arguments: ");
            for (String argument : remainder)
            {
                System.out.print(argument);
                System.out.print(" ");
            }

            System.out.println();
        }

    }
    catch (ParseException exception)
    {
        System.out.print("Parse error: ");
        System.out.println(exception.getMessage());
    }

}

Output:

usage: CLIsample [-A <opt3>] [-r <opt1>] [-S <opt2>] [--test]
                 [<arg1> [<arg2> [<arg3> ...
       Options, flags and arguments may be in any order
 -A <opt3>   The A option
 -r <opt1>   The r option
 -S <opt2>   The S option
    --test   The test option
This is DwB's solution brought to Commons CLI 1.3.1 compliance (deprecated
methods replaced)
Option A is present.  The value is: opt3
Option r is present.  The value is: opt1
Option S is present.  The value is: opt2
Option test is present.  This is a flag option.
Remaining arguments: arg1 arg2 arg3 arg4
Panjandrum answered 14/4, 2016 at 20:20 Comment(0)
C
2

You could use https://github.com/jankroken/commandline , here's how to do that:

To make this example work, I must make assumptions about what the arguments means - just picking something here...

-r opt1 => replyAddress=opt1
-S opt2 arg1 arg2 arg3 arg4 => subjects=[opt2,arg1,arg2,arg3,arg4]
--test = test=true (default false)
-A opt3 => address=opt3

this can then be set up this way:

public class MyProgramOptions {
  private String replyAddress;
  private String address;
  private List<String> subjects;
  private boolean test = false;

  @ShortSwitch("r")
  @LongSwitch("replyAddress") // if you also want a long variant. This can be skipped
  @SingleArgument
  public void setReplyAddress(String replyAddress) {
    this.replyAddress = replyAddress;
  }

  @ShortSwitch("S")
  @AllAvailableArguments
  public void setSubjects(List<String> subjects) {
    this.subjects = subjects;
  }

  @LongSwitch("test")
  @Toggle(true)
  public void setTest(boolean test) {
    this.test = test;
  }

  @ShortSwitch("A")
  @SingleArgument
  public void setAddress(String address) {
    this.address = address;
  }

  // getters...
}

and then in the main method, you can just do:

public final static void main(String[] args) {
  try {
    MyProgramOptions options = CommandLineParser.parse(MyProgramOptions.class, args, OptionStyle.SIMPLE);

    // and then you can pass options to your application logic...

  } catch
    ...
  }
}
Corral answered 8/3, 2014 at 3:23 Comment(0)
S
1

You could use the refcodes-console artifact at refcodes-console on REFCODES.ORG:

Option<String> r     = new StringOptionImpl( "-r", null, "opt1", "..." );
Option<String> s     = new StringOptionImpl( "-S", null, "opt2", "..." );
Operand<String> arg1 = new StringOperandImpl( "arg1", "..." );
Operand<String> arg2 = new StringOperandImpl( "arg2", "..." );
Operand<String> arg3 = new StringOperandImpl( "arg3", "..." );
Operand<String> arg4 = new StringOperandImpl( "arg4", "..." );
Switch test          = new SwitchImpl( null, "--test", "..." );
Option<String> a     = new StringOptionImpl( "-A", null, "opt3", "..." );
Condition theRoot    = new AndConditionImpl( r, s, a, arg1, arg2, arg3, arg4,
    test );

Create your arguments parser ArgsParserImpl with your root condition:

ArgsParser theArgsParser = new ArgsParserImpl( theRoot );
theArgsParser.setName( "MyProgramm" );
theArgsParser.setSyntaxNotation( SyntaxNotation.GNU_POSIX );

Above you define your syntax, below you invoke the parser:

theArgsParser.printUsage();
theArgsParser.printSeparatorLn();
theArgsParser.printOptions();
theArgsParser.evalArgs( new String[] {
    "-r", "RRRRR", "-S", "SSSSS", "11111", "22222", "33333", "44444", 
    "--test", "-A", "AAAAA"
} );

In case you provided some good descriptions, theArgsParser.printUsage() will even show you the pretty printed usage:

Usage: MyProgramm -r <opt1> -S <opt2> -A <opt3> arg1 arg2 arg3 arg4 --test

In the above example all defined arguments must be passed by the user, else the parser will detect a wrong usage. In case the --test switch is to be optional (or any other argument), assign theRoot as follows:

theRoot = new AndConditionImpl( r, s, a, arg1, arg2, arg3, arg4, new OptionalImpl( test ) );

Then your syntax looks as follows:

Usage: MyProgramm -r <opt1> -S <opt2> -A <opt3> arg1 arg2 arg3 arg4 [--test]

The full example for your case you find in the StackOverFlowExamle. You can use AND, OR, XOR conditions and any kind of nesting ... hope this helps.

Evaluate the parsed arguments as follows: r.getValue() ); or if (test.getValue() == true) ...:

LOGGER.info( "r    :=" + r.getValue() );
LOGGER.info( "S    :=" + s.getValue() );
LOGGER.info( "arg1 :=" + arg1.getValue() );
LOGGER.info( "arg2 :=" + arg2.getValue() );
LOGGER.info( "arg3 :=" + arg3.getValue() );
LOGGER.info( "arg4 :=" + arg4.getValue() );
LOGGER.info( "test :=" + test.getValue() + "" );
LOGGER.info( "A    :=" + a.getValue() );
Somme answered 6/4, 2015 at 10:23 Comment(3)
Oh boy your library pulls in many deps. I stopped counting. Sorry, won't do for me.Lactobacillus
You are right. I fixed this with version 1.1.0 released just now to maven central [1]. The compile time dependencies excluding the refcodes dependencies[2] I determine with this command: mvn dependency:list -DincludeScope=compile -DexcludeScope=test | grep -v refcodes Removing one false positive [3] from the result, I end up with those dependencies for version 1.1.0: jline:jline:jar:2.14.2:compile log4j:log4j:jar:1.2.17:compile Thanks for the input & hope it helps! :-)Somme
[1] See the refcodes change-list for version 1.1.0. [2] Them refcodes artifacts are of one source, one version, all released in sync and split into modules as of separation of concerns and modularization, moreover all are located at bitbucket.org/refcodes. [3] See Why does dependency:list ... lists compile scope for transitive dependencies of test scope itemsSomme
H
0

Ok, thanks to Charles Goodwin for the concept. Here is the answer:

import java.util.*;
public class Test {

  public static void main(String[] args) {
     List<String> argsList  = new ArrayList<String>();  
     List<String> optsList  = new ArrayList<String>();
     List<String> doubleOptsList  = new ArrayList<String>();
     for (int i=0; i < args.length; i++) {
         switch (args[i].charAt(0)) {
         case '-':
             if (args[i].charAt(1) == '-') {
                 int len = 0;
                 String argstring = args[i].toString();
                 len = argstring.length();
                 System.out.println("Found double dash with command " +
                     argstring.substring(2, len) );
                 doubleOptsList.add(argstring.substring(2, len));           
             } else {
                 System.out.println("Found dash with command " + 
                   args[i].charAt(1) + " and value " + args[i+1] );   
                 i= i+1;
                 optsList.add(args[i]);      
             }           
         break;         
         default:            
         System.out.println("Add a default arg." );
         argsList.add(args[i]);
         break;         
         }     
     } 
  } 

}
Hygrothermograph answered 8/9, 2011 at 0:16 Comment(2)
It's still not quite right because you're not storing the -flag opt combination.Hydrocele
I wrote it out as a Beanshell solution: #7357479Hygrothermograph
G
0

Also you can do it with ParameterTool

import org.apache.flink.api.java.utils.ParameterTool;        

        public static void main(String... args) {
        ParameterTool parameters = ParameterTool.fromArgs(args);
        parameters.get("some_key");
        }
Gnomic answered 5/7, 2024 at 5:3 Comment(0)
F
-1

Simple code for command line in java:

class CMDLineArgument
{
    public static void main(String args[])
    {
        String name=args[0];
        System.out.println(name);
    }
}
Fatimafatimah answered 9/2, 2014 at 13:41 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.