问题
If I have 2 options defined as required such as:
public static void main(String [] args){
Options options= new Options();
Option inputFileOp=Option.builder("i").longOpt("input").hasArg().desc("Input file").argName("file").required().build();
options.addOption(inputFileOp);
Option outputFileOp=Option.builder("o").longOpt("output").hasArg().desc("Output file").argName("file").required().build();
options.addOption(outputFileOp);
and a help option
Option helpOp =new Option("h",false,"Show Help");
helpOp.setLongOpt("help");
helpOptions.addOption(helpOp);
and parser
DefaultParser parser = new DefaultParser();
CommandLine cmd=parser.parse(options,args);
if(cmd.hasOption(helpOp.getOpt())){
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp( "MyApp.sh", options );
System.exit(0);
}
}
When the user input for example myApp -h .. An exception is raised in the parsing step that there are missing required options, While I want to print the help data.
How to allow calling the help while keeping these options declared as required?
回答1:
The code for DefaultParser
appears to always call the checkRequiredArgs()
method. Which seems to indicate that you cannot, in one fell swoop, avoid the problem.
The way we have addressed this situation in the past, perhaps in a suboptimal fashion, is to parse the command line twice. The parsing is fast, so the overhead it minimal.
We created a method checkForHelp(String[] args)
that takes the (String[] args). It adds only the help option to an options, parses the command line, and then ascertains whether help is specified. If so, the help is printed, and the program exits. Otherwise, the full set of options is processed. This approach allows for the required fields to be processed as expected. Note that the help option must also be added in the main list.
public static Option helpOption = Option.builder("h")
.longOpt("help")
.required(false)
.hasArg(false)
.build();
public static boolean checkForHelp(String[] args) throws ParseException {
boolean hasHelp = false;
Options options = new Options();
try {
options.addOption(helpOption);
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
if (cmd.hasOption(helpOption.getOpt())) {
hasHelp = true;
}
}
catch (ParseException e) {
throw e;
}
return hasHelp;
}
Then in the main
method, something akin to:
options.addOption(hostOption);
options.addOption(portOption);
options.addOption(serviceNameOption);
options.addOption(helpOption); // <-- must also be here to avoid exception
try {
if (checkForHelp(args)) {
HelpFormatter fmt = new HelpFormatter();
fmt.printHelp("Help", options);
return;
}
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args);
if (cmd.hasOption("host")) {
host = cmd.getOptionValue("host");
System.out.println(host); // gets in here but prints null
}
if (cmd.hasOption("port")) {
port = ((Number) cmd.getParsedOptionValue("port")).intValue();
System.out.println(port); // gets in here but throws a null
// pointer exception
}
if (cmd.hasOption("service_name")) {
serviceName = cmd.getOptionValue("service_name");
System.out.println(serviceName); // gets in here but prints null
}
}
catch (Exception e) {
e.printStackTrace();
}
EDIT: as it turns out, this approach is similar to the answer provided here: Commons CLI required groups. I guess I feel better that our approach has others supporting what we believed.
EDIT2: In a quick test, I believe the problem of having required options may be avoided by using an "OptionGroup". Here is a revised checkForHelp
that works by adding all of the options to an OptionGroup. In my quick testing, it avoids the problem that was present if one did, e.g., ("--arg1 --help").
public static boolean checkForHelp(String[] args) throws ParseException
{
boolean hasHelp = false;
Options options = new Options();
try {
options.addOption(hostOption); //has required set
options.addOption(portOption);
options.addOption(serviceNameOption);
options.addOption(helpOption);
// create an option group
OptionGroup og = new OptionGroup();
og.addOption(hostOption);
og.addOption(portOption);
og.addOption(serviceNameOption);
og.addOption(helpOption);
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args, false);
if (cmd.hasOption(helpOption.getOpt()) || cmd.hasOption(helpOption.getLongOpt())) {
hasHelp = true;
}
}
catch (ParseException e) {
throw e;
}
return hasHelp;
}
回答2:
Wouldn't it be easier to just parse raw args and check for --help/-h
or help.getOpt()/help.getLongOpt()
keyword before parsing using DefaultParser
? This way you avoid double parsing overhead.
for (String s : args) {
if (s.equals("-h") || s.equals("--help")) { // or use help.getOpt() || help.getLongOpt()
formatter.printHelp("ApplicationName", arguments);
System.exit(1);
}
}
回答3:
Add another method, as the previous poster said, but make sure you stop at the first unrecognized argument.
private static boolean hasHelp(final Option help, final String[] args) throws ParseException
{
Options options = new Options();
options.addOption(help);
CommandLineParser parser = new DefaultParser();
CommandLine cmd = parser.parse(options, args, true);
return cmd.hasOption(help.getOpt());
}
If stopAtNonOption
is set to false, then this function would throw for valid arguments, like java -jar app.jar -doStuff
来源:https://stackoverflow.com/questions/36720946/apache-cli-required-options-contradicts-with-help-option