当前位置: 首页 > 工具软件 > JCommander > 使用案例 >

JCommander(命令行参数解析工具)

蔺山
2023-12-01

Because life is too short to parse command line parameters. From JCommander official site: http://jcommander.org/


以下基本翻译自jcommander官网


JCommander是一个用于解析命令行参数的Java框架,支持解析所有基本的数据类型,也支持将命令行解析成用户自定义的类型,只需要写一个转变函数。

1. JCommander用法

将用来表示参数的fields用@Parameter标识:

import com.beust.jcommander.Parameter;
 
public class JCommanderExample {
  @Parameter
  private List<String> parameters = new ArrayList<String>();
 
  @Parameter(names = { "-log", "-verbose" }, description = "Level of verbosity")
  private Integer verbose = 1;
 
  @Parameter(names = "-groups", description = "Comma-separated list of group names to be run")
  private String groups;
 
  @Parameter(names = "-debug", description = "Debug mode")
  private boolean debug = false;
}

JCommander就可以解析命令行中的参数了:

JCommanderExample jct = new JCommanderExample();
String[] argv = { "-log", "2", "-groups", "unit" };
new JCommander(jct, argv);
 
Assert.assertEquals(jct.verbose.intValue(), 2);


2. 选项类型

Boolean:

@Parameter(names = "-debug", description = "Debug mode", arity = 1)
private boolean debug = true;
boolean类型默认arity=0,这样的话不要用户为参数设置value,只要参数中有-debug就被parse成true,没有就parse成false。但是如果加上 arity=1,则需要用户明确指定value:

program -debug true
program -debug false


String,Interger,Long:

对这几种类型,JCommander会尝试parse随后的参数,并上溯成参数的类型,如果参数不能成功上溯则会产生exception

@Parameter(names = "-log", description = "Level of verbosity")
private Integer verbose = 1;
java Main -log 3
//got exception for the following one
java Main -log text


Lists:

如果类型是List,则这个参数可出现多次

@Parameter(names = "-host", description = "The host")
private List<String> hosts = new ArrayList<String>();
下例中fields hosts包含了两个字符串

java Main -host host1 -verbose -host host2


Password:

指明password为true表明这个field是password,我们不想他出现在命令行上,遇到password的参数,用户会被要求在prompt中输入密码

public class ArgsPassword {
  @Parameter(names = "-password", description = "Connection password", password = true)
  private String password;
}


Echo Input:

当你想让password显示在prompt中时,需要设置echoInput=true,这个setting只有在password=true时才有效

public class ArgsPassword {
  @Parameter(names = "-password", description = "Connection password", password = true, echoInput = true)
  private String password;
}



3. 自定义类型

当需要更负责的参数时,比如files,host names,lists等,我们可以写一个转换器(converter)来将参数转换成我们想要的格式或类型。转换器需要实现IStringConverter接口

public interface IStringConverter<T> {
  T convert(String value);
}

a. 通过annotation实现:

下面的converter将string转换成file

public class FileConverter implements IStringConverter<File> {
  @Override
  public File convert(String value) {
    return new File(value);
  }
}

将参数对应的field定义成下面这样,注意要在annotation里指定converter

@Parameter(names = "-file", converter = FileConverter.class)
File file;

b. 通过工厂 factory 实现:

在每个parameter annotation中都指定converter有点烦,可以通过factory解决这个问题。

比如你要将下面的参数解析成host和port java App -target example.com:8080
定义一个类来放参数

public class HostPort {
  private String host;
  private Integer port;
}

写个converter把参数String转换成HostPort

class HostPortConverter implements IStringConverter<HostPort> {
  @Override
  public HostPort convert(String value) {
    HostPort result = new HostPort();
    String[] s = value.split(":");
    result.host = s[0];
    result.port = Integer.parseInt(s[1]);
 
    return result;
  }
}
写一个Factory,作用是返回converter

public class Factory implements IStringConverterFactory {
  public Class<? extends IStringConverter<?>> getConverter(Class forType) {
    if (forType.equals(HostPort.class)) return HostPortConverter.class;
    else return null;
  }
参数类也很简单,参数类型直接用HostPort就好了

public class ArgsConverterFactory {
  @Parameter(names = "-hostport")
  private HostPort hostPort;
}
没有看到Factory用在什么地方?是在你的程序创建JCommander时传进去的

ArgsConverterFactory a = new ArgsConverterFactory();
JCommander jc = new JCommander(a);
jc.addConverterFactory(new Factory());
jc.parse("-hostport", "example.com:8080");
 
Assert.assertEquals(a.hostPort.host, "example.com");
Assert.assertEquals(a.hostPort.port.intValue(), 8080);



4. 参数验证
通过提供一个实现了IParameterValidator接口的类,可以让JCommander验证你的参数

public interface IParameterValidator {
 /**
   * Validate the parameter.
   *
   * @param name The name of the parameter (e.g. "-host").
   * @param value The value of the parameter that we need to validate
   *
   * @throws ParameterException Thrown if the value of the parameter is invalid.
   */
  void validate(String name, String value) throws ParameterException;
}
下面这个validator可以验证提供的参数为正整数

public class PositiveInteger implements IParameterValidator {
 public void validate(String name, String value)
      throws ParameterException {
    int n = Integer.parseInt(value);
    if (n < 0) {
      throw new ParameterException("Parameter " + name + " should be positive (found " + value +")");
    }
  }
}
在parameter annotation中用validateWith来指定验证器

@Parameter(names = "-age", validateWith = PositiveInteger.class)
private Integer age;



5. 主参数

我们可以定义一个main parameter来放所有参数,这个参数不用指定names,必须为List<String>类型

@Parameter(description = "Files")
private List<String> files = new ArrayList<String>();
 
@Parameter(names = "-debug", description = "Debugging level")
private Integer debug = 1;
run下面命令行,files会保存‘file1’和‘file2’

java Main -debug file1 file2



6. 私有参数

参数可以是private的

public class ArgsPrivate {
  @Parameter(names = "-verbose")
  private Integer verbose = 1;
 
  public Integer getVerbose() {
    return verbose;
  }
}


ArgsPrivate args = new ArgsPrivate();
new JCommander(args, "-verbose", "3");
Assert.assertEquals(args.getVerbose().intValue(), 3);



7. 参数分隔符

参数默认用空格分隔,但是我们也可以指定自己的分隔符,比如用“:”, “=”等,java Main -log:3
可以在参数类定义中使用@Parameter annotation指定分隔符

@Parameters(separators = "=")
public class SeparatorEqual {
  @Parameter(names = "-level")
  private Integer level = 2;
}


8. 多重描述(多个参数类)

你可以将参数放在不同的参数类中,比如定义以下两个含有不同参数的参数类

public class ArgsMaster {
  @Parameter(names = "-master")
  private String master;
}
public class ArgsSlave {
  @Parameter(names = "-slave")
  private String slave;
}
使用时将两个类都传入JCommander

ArgsMaster m = new ArgsMaster();
ArgsSlave s = new ArgsSlave();
String[] argv = { "-master", "master", "-slave", "slave" };
new JCommander(new Object[] { m , s }, argv);
 
Assert.assertEquals(m.master, "master");
Assert.assertEquals(s.slave, "slave");


9. @语法

JCommander支持从文件中获取并解析参数,比如文件/tmp/params中包含了以下内容

-verbose
file1
file2
file3
在命令行中用@指定文件路径即可

java Main @/tmp/parameters



10. 参数value数量(Arities)

有时候你想为一个参数设置多个值,这时就要用到arity,比如-pairs需要两个values,则设置arity=2

@Parameter(names = "-pairs", arity = 2, description = "Pairs")
private List<String> pairs;
JCommander就会为pairs接收两个values

java Main -pairs slave master
注意想接收多个value的参数需要设置成List<String>类型,boolean类型默认arity为0,String、Integer、int、Long、long默认arity都为1。


可变参数值数量,设置variableArity=true,你可以指定一个参数接收不定数量的values

@Parameter(names = "-foo", variableArity = true)
public List<String> foo = new ArrayList<String>();
下面两个例子一个接收2个参数,一个接收3个参数

program -foo a1 a2 a3 -bar
program -foo a1 -bar



11. 多重选项名

同一个选项(option)可以有不同别名,比如--recursive,可以有一个缩写-r,可以通过给names设置多个值实现

@Parameter(names = { "-d", "--outputDirectory" }, description = "Directory")
private String outputDirectory;
下面两种都可以给outputDirectory赋值

</pre><pre name="code" class="java">java Main -d /tmp
java Main --outputDirectory /tmp


12. 其他选项配置

JCommander#setCaseSensitiveOptions(boolean):选项是否大小写敏感,如果设置成true,则‘-option' 和'-OPTION'是不同的选项

JCommander#setAllowAbbreviatedOptions(boolean):打开选项缩写功能,’-op‘可以表示’-option‘选项,如果选项缩写太模糊不能定位到某个选项,则会throw一个ParameterException



13. 必须和可选选项

@Parameter annotation的required设置为true则表示此选项为必须的,否则为可选(默认为可选)

@Parameter(names = "-host", required = true)
private String host;



14. 默认值

一般情况下直接给选项赋值就可以了

private Integer logLevel = 3;
如果有更复杂的情况,比如对某些选项可以再不同的类里重复使用,或者你想把所有的default value都存在同一个地方比如一个XML文件,这些可以通过实现IDefaultProvider接口达成

public interface IDefaultProvider {
  /**
   * @param optionName The name of the option as specified in the names() attribute
   * of the @Parameter option (e.g. "-file").
   *
   * @return the default value for this option.
   */
  String getDefaultValueFor(String optionName);
}
实例

//这是个内部类
private static final IDefaultProvider DEFAULT_PROVIDER = new IDefaultProvider() {
  @Override
  public String getDefaultValueFor(String optionName) {
    return "-debug".equals(optionName) ? "false" : "42";
  }
};
 
// ...
 
JCommander jc = new JCommander(new Args());
jc.setDefaultProvider(DEFAULT_PROVIDER); //把default provider传入



15. 帮助选项

非常简单

@Parameter(names = "--help", help = true)
private boolean help;



16. 更多复杂的命令语法

想git之类的工具,除了git命令本身需要选项,它还包含一些子命令,如git commit,子命令本身也包含一些选项

git commit --amend -m "Bug fix"
上面的命令中,commit在JCommander中叫做’commands‘, 我们要为这个子命令单独创建一个类

@Parameters(separators = "=", commandDescription = "Record changes to the repository")
private class CommandCommit {
 
  @Parameter(description = "The list of files to commit")
  private List<String> files;
 
  @Parameter(names = "--amend", description = "Amend")
  private Boolean amend = false;
 
  @Parameter(names = "--author")
  private String author;
}

再为另一个git命令add创建一个类

@Parameters(commandDescription = "Add file contents to the index")
public class CommandAdd {
 
  @Parameter(description = "File patterns to add to the index")
  private List<String> patterns;
 
  @Parameter(names = "-i")
  private Boolean interactive = false;
}

创建好后要在JCommander对象里通过addCommand注册这些命令,当然我们也可以创建一个主要的参数类来接受git的参数

CommandMain cm = new CommandMain();
JCommander jc = new JCommander(cm);
 
CommandAdd add = new CommandAdd();
jc.addCommand("add", add);
CommandCommit commit = new CommandCommit();
jc.addCommand("commit", commit);
 
jc.parse("-v", "commit", "--amend", "--author=cbeust", "A.java", "B.java");
 
Assert.assertTrue(cm.verbose);
Assert.assertEquals(jc.getParsedCommand(), "commit");
Assert.assertTrue(commit.amend);
Assert.assertEquals(commit.author, "cbeust");
Assert.assertEquals(commit.files, Arrays.asList("A.java", "B.java"));



17. Exception

JCommander发现错误时会抛出ParameterExcpetion,这是一个runtime exception



18. usage

可以再JCommander实例中调用usage()方法获取命令行使用方法



19. 隐藏参数

如果不想某些选项出现在usage里,可以隐藏他们

@Parameter(names = "-debug", description = "Debug mode", hidden = true)
private boolean debug = false;


20. 国际化

选项的描述(description)可以国际化,首先在类上使用@Parameter指定消息bundle的名字,然后在选项的@Parameter中使用descriptionKey指定bundle中的key

@Parameters(resourceBundle = "MessageBundle")
private class ArgsI18N2 {
  @Parameter(names = "-host", description = "Host", descriptionKey = "host")
  String hostName;
}
在MessageBundle中,定义host如下

host:主机


21. 参数代理

使用@ParameterDelegate可以在主参数类中引用其他的代理参数类

class Delegate {
  @Parameter(names = "-port")
  private int port;
}
 
class MainParams {
  @Parameter(names = "-v")
  private boolean verbose;
 
  @ParametersDelegate
  private Delegate delegate = new Delegate();
}
使用时只需在JCommander中指定主参数类

MainParams p = new MainParams();
new JCommander(p).parse("-v", "-port", "1234");
Assert.assertTrue(p.isVerbose);
Assert.assertEquals(p.delegate.port, 1234);


22. 动态参数

JCommander允许接收没有预先定义的选项,比如’-Da=b -Dc=d‘, Da和Dc没有在参数类中定义,这些参数要用@DynamicParameter annotation标记的类型为Map<String, String>的选项接收,动态参数可以重复多次

@DynamicParameter(names = "-D", description = "Dynamic parameters go here")
private Map<String, String> params = new HashMap<String, String>();
通过使用assignment属性,可以用其他符号代替’=‘作为赋值符号








java Main --outputDirectory /tmp
 类似资料: