将参数解析为Java命令行程序

67

如果我想要解析这个:

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

我在程序中想要的结果是:

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

我更倾向于使用Apache Commons CLI,但是文档对我提出的情况并不太清楚。具体来说,文档没有告诉你如何处理我下面指定的第三种类型的选项:

1. options with a "-" char
2. options with a "--" char
3. options without any marker, or "bare args"
我希望 Apache Commons CLI 可以正常工作,但仍然能够将没有选项前缀的普通参数传递给程序。也许它可以实现,但是我在阅读文档时没有找到相关说明...

2
可能是如何解析Java中的命令行参数?的重复问题。 - xenteros
9个回答

81

使用 Apache Commons CLI 库 的 commandline.getArgs() 方法获取参数 arg1、arg2、arg3 和 arg4。这是一些示例代码:



    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());
        }
    }


2
此答案中的某些方法已不再受支持。当前 CLI 版本的示例:https://commons.apache.org/proper/commons-cli/usage.html - stuart
这里有一个更新的实现方式:点击此处 - Yar

35

你可以手动完成它。

NB:使用HashMap代替内部类可能会更好。

/** 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
}

1
你应该检查 args[i] 的长度! - Jon Bright
我正打算输入这个!如果您想花点心思,也可以使用哈希表而不是 switch 语句。 - IslandCow
“-flag opt combination”是什么意思?你是指短横线后面跟着任意数量的字符吗? - djangofan
2
好的,我会接受这个答案,尽管我认为optsLists应该转换为HashMap。 - djangofan
for循环中,你需要初始化迭代器(int i = 0;),否则编译器就会抱怨(Java 7)。 - theUg

19

我喜欢这个。它简单,而且你可以为每个参数有多个参数:

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;
    }
}
例如:
-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

2
我很惊讶居然没有一个库以这种优雅的方式实现。其他所有库都会在某种程度上损害你的代码(冗余代码、可变状态、额外的构造函数调用)。 - Sridhar Sarnobat
增强请求 - 你能修改这个答案以接受等于分隔符吗? :) 我可以自己猜测,但我宁愿让你来做。 - Sridhar Sarnobat

15

我意识到这个问题提到了对Commons CLI的偏好,但我猜想,当这个问题被提出时,Java命令行解析库的选择不是很多。但九年后的2020年,你不会更愿意像下面这样编写代码吗?

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));
    }
}

通过运行问题中的命令来执行:

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

我喜欢这段代码的原因是:

  • 紧凑 - 没有样板代码
  • 声明性 - 使用注释而不是构建器API
  • 强类型 - 注释字段可以是任何类型,而不仅仅是字符串
  • 没有重复 - 选项声明和解析结果获取在注释字段中一起完成
  • 清晰 - 注释比命令式代码更能表达意图
  • 责任分离 - call方法中的业务逻辑与解析相关的逻辑无关
  • 方便 - 主函数中的一行代码连接解析器并运行可调用对象中的业务逻辑
  • 功能强大 - 内置的--help--version选项自动提供使用方式和版本帮助
  • 用户友好 - 使用帮助消息使用颜色来区分重要元素(如选项名称)和其余部分的使用帮助,以减轻用户的认知负荷

当您使用picocli(https://picocli.info)库时,上述功能仅是您获得的一部分。

请注意,作者对picocli毫无保留的偏爱、完全、极度。 :-) 但是,我确实相信,在2020年,我们有比Commons CLI更好的选择来构建命令行应用程序。


8

这里是@DwB的解决方案,已升级为Commons CLI 1.3.1兼容性(替换了过时的OptionBuilder和GnuParser组件)。Apache文档使用的示例在现实中具有未标记/裸参数,但忽略了它们。感谢@DwB展示了它如何工作。

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());
    }

}

输出:

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

2
你可以使用 https://github.com/jankroken/commandline,以下是如何操作的:
为了让这个示例起作用,我必须假设参数的含义 - 只是在这里随便选一些...
-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

这可以这样设置:
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...
}

然后在主方法中,你只需要这样做:

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
    ...
  }
}

1
你可以在REFCODES.ORG上使用refcodes-console工件,链接为refcodes-console
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 );

创建你的参数解析器ArgsParserImpl,并设置根条件:
ArgsParser theArgsParser = new ArgsParserImpl( theRoot );
theArgsParser.setName( "MyProgramm" );
theArgsParser.setSyntaxNotation( SyntaxNotation.GNU_POSIX );

在上面定义你的语法,在下面调用解析器:
theArgsParser.printUsage();
theArgsParser.printSeparatorLn();
theArgsParser.printOptions();
theArgsParser.evalArgs( new String[] {
    "-r", "RRRRR", "-S", "SSSSS", "11111", "22222", "33333", "44444", 
    "--test", "-A", "AAAAA"
} );

如果您提供了一些良好的描述,theArgsParser.printUsage()甚至会向您展示漂亮的用法打印输出:
Usage: MyProgramm -r <opt1> -S <opt2> -A <opt3> arg1 arg2 arg3 arg4 --test

在上面的示例中,所有定义的参数必须由用户传递,否则解析器将检测到错误的用法。如果要将--test开关设置为可选(或任何其他参数),请按如下方式分配theRoot

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

然后您的语法如下所示:

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

您可以在StackOverFlowExamle中找到与您情况相关的完整示例。您可以使用AND、OR、XOR条件以及任何类型的嵌套…希望这能帮助到您。

按以下方式评估解析后的参数:r.getValue() );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() );

1
哦,天啊,你的库引入了很多依赖项。我已经停止计数了。抱歉,这对我来说不可行。 - peterh
你说得对。我刚刚发布了版本1.1.0到Maven中央库[1],已经修复了这个问题。我使用以下命令确定编译时依赖项(不包括refcodes依赖项[2]):mvn dependency:list -DincludeScope=compile -DexcludeScope=test | grep -v refcodes从结果中删除一个误报[3],最终得到版本1.1.0的以下依赖项:jline:jline:jar:2.14.2:compile log4j:log4j:jar:1.2.17:compile感谢您的反馈,希望这能有所帮助! :-) - REFCODES.ORG
[1] 查看版本1.1.0的refcodes change-list。[2] 这些refcodes工件都是来自同一源头、同一版本,全部同步发布并分割成模块以实现关注点分离和模块化。此外,它们都位于https://bitbucket.org/refcodes。[3] 参见为什么dependency:list ...会列出测试范围项的传递依赖项的编译范围 - REFCODES.ORG

0

好的,感谢Charles Goodwin提供的概念。这是答案:

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;         
         }     
     } 
  } 

}

1
还不太对,因为你没有存储“-flag opt”组合。 - Charles Goodwin
我将其编写为Beanshell解决方案:http://stackoverflow.com/questions/7356979/java-beanshell-scripting-with-args-to-the-the-program - djangofan

0
Java命令行的简单代码:
class CMDLineArgument
{
    public static void main(String args[])
    {
        String name=args[0];
        System.out.println(name);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接