Similar to this thread for C#, I need to split a string containing the command line arguments to my program so I can allow users to easily run multiple commands. For example, I might have the following string:
类似于c#的这个线程,我需要将一个包含命令行参数的字符串拆分为我的程序,这样我就可以允许用户轻松地运行多个命令。例如,我可能有以下字符串:
-p /path -d "here's my description" --verbose other args
Given the above, Java would normally pass the following in to main:
在上述情况下,Java通常会将以下内容传递给main:
Array[0] = -p
Array[1] = /path
Array[2] = -d
Array[3] = here's my description
Array[4] = --verbose
Array[5] = other
Array[6] = args
I don't need to worry about any shell expansion, but it must be smart enough to handle single and double quotes and any escapes that may be present within the string. Does anybody know of a way to parse the string as the shell would under these conditions?
我不需要担心任何shell扩展,但是它必须足够智能,能够处理单引号和双引号以及字符串中可能存在的任何转义。有没有人知道一种方法来解析弦,就像在这些条件下的壳一样?
NOTE: I do NOT need to do command line parsing, I'm already using joptsimple to do that. Rather, I want to make my program easily scriptable. For example, I want the user to be able to place within a single file a set of commands that each of which would be valid on the command line. For example, they might type the following into a file:
注意:我不需要执行命令行解析,我已经在使用joptsimple来完成它。相反,我想让我的程序易于编写。例如,我希望用户能够在一个文件中放置一组命令,每个命令在命令行上都是有效的。例如,他们可以将下列文件输入到文件中:
--addUser admin --password Admin --roles administrator,editor,reviewer,auditor
--addUser editor --password Editor --roles editor
--addUser reviewer --password Reviewer --roles reviewer
--addUser auditor --password Auditor --roles auditor
Then the user would run my admin tool as follows:
然后用户运行我的管理工具如下:
adminTool --script /path/to/above/file
main()
will then find the --script
option and iterate over the different lines in the file, splitting each line into an array that I would then fire back at a joptsimple instance which would then be passed into my application driver.
main()将找到-script选项并遍历文件中的不同行,将每一行分割成一个数组,然后在一个joptsimple实例中进行攻击,然后将该实例传递给我的应用程序驱动程序。
joptsimple comes with a Parser that has a parse method, but it only supports a String
array. Similarly, the GetOpt constructors also require a String[]
-- hence the need for a parser.
joptsimple附带了一个具有解析方法的解析器,但它只支持一个字符串数组。类似地,GetOpt构造函数也需要一个字符串[]——因此需要一个解析器。
7 个解决方案
#1
23
Here is a pretty easy alternative for splitting a text line from a file into an argument vector so that you can feed it into your options parser:
这里有一个很简单的方法,可以将文本行从文件分割成一个参数向量,这样您就可以将它输入到您的选项解析器中:
This is the solution:
这是解决方案:
public static void main(String[] args) {
String myArgs[] = Commandline.translateCommandline("-a hello -b world -c \"Hello world\"");
for (String arg:myArgs)
System.out.println(arg);
}
The magic class Commandline
is part of ant. So you either have to put ant on the classpath or just take the Commandline class as the used method is static.
魔法类命令行是ant的一部分。所以你要么把ant放在类路径上,要么把Commandline类作为使用的方法是静态的。
#2
9
You should use a fully featured modern object oriented Command Line Argument Parser I suggest my favorite Java Simple Argument Parser. And how to use JSAP, this is using Groovy as an example, but it is the same for straight Java. There is also args4j which is in some ways more modern than JSAP because it uses annotations, stay away from the apache.commons.cli stuff, it is old and busted and very procedural and un-Java-eques in its API. But I still fall back on JSAP because it is so easy to build your own custom argument handlers.
您应该使用一个功能齐全的现代面向对象的命令行参数解析器,我建议使用我最喜欢的Java简单参数解析器。以及如何使用JSAP,这是用Groovy作为示例,但对于纯Java也是如此。还有args4j,它在某些方面比JSAP更现代,因为它使用注释,远离apache.common .cli之类的东西,它在API中是陈旧的、被破坏的、非常程序化的和非java的。但是我仍然依赖于JSAP,因为构建自己的自定义参数处理程序非常容易。
There are lots of default Parsers for URLs, Numbers, InetAddress, Color, Date, File, Class, and it is super easy to add your own.
对于url、数字、InetAddress、颜色、日期、文件、类,有很多默认解析器,添加自己的内容非常简单。
For example here is a handler to map args to Enums:
例如,这里有一个处理args到Enums的处理程序:
import com.martiansoftware.jsap.ParseException;
import com.martiansoftware.jsap.PropertyStringParser;
/*
This is a StringParser implementation that maps a String to an Enum instance using Enum.valueOf()
*/
public class EnumStringParser extends PropertyStringParser
{
public Object parse(final String s) throws ParseException
{
try
{
final Class klass = Class.forName(super.getProperty("klass"));
return Enum.valueOf(klass, s.toUpperCase());
}
catch (ClassNotFoundException e)
{
throw new ParseException(super.getProperty("klass") + " could not be found on the classpath");
}
}
}
and I am not a fan of configuration programming via XML, but JSAP has a really nice way to declare options and settings outside your code, so your code isn't littered with hundreds of lines of setup that clutters and obscures the real functional code, see my link on how to use JSAP for an example, less code than any of the other libraries I have tried.
和我不是一个喜欢配置通过XML编程,但是JSAP有一个很好的方法声明以外的选项和设置您的代码,所以您的代码不是散落着数百行设置杂波和掩盖了真正的功能代码,看到我的链接如何使用JSAP为例,更少的代码比任何其他库我已经试过了。
This is a direction solution to your problem as clarified in your update, the lines in your "script" file are still command lines. Read them in from the file line by line and call JSAP.parse(String);
.
这是您的问题的一个方向解决方案,在您的更新中澄清,您的“脚本”文件中的行仍然是命令行。从文件中逐行读取它们,并调用JSAP.parse(String);
I use this technique to provide "command line" functionality to web apps all the time. One particular use was in a Massively Multiplayer Online Game with a Director/Flash front end that we enabled executing "commands" from the chat like and used JSAP on the back end to parse them and execute code based on what it parsed. Very much like what you are wanting to do, except you read the "commands" from a file instead of a socket. I would ditch joptsimple and just use JSAP, you will really get spoiled by its powerful extensibility.
我一直使用这种技术为web应用程序提供“命令行”功能。一个特别的用途是在一个大型多人在线游戏中使用一个Director/Flash前端,我们可以从聊天中执行“命令”,然后在后台使用JSAP来解析它们,并根据解析的内容执行代码。很像你想要做的,除非你从一个文件而不是一个套接字读取“命令”。我会抛弃joptsimple,只使用JSAP,你会被它强大的可扩展性所破坏。
#3
8
If you need to support only UNIX-like OSes, there is an even better solution. Unlike Commandline
from ant, ArgumentTokenizer from DrJava is more sh
-like: it supports escapes!
如果您只需要支持类unix的OSes,那么还有一个更好的解决方案。不同于ant的命令行,来自DrJava的ArgumentTokenizer更像sh:它支持转义!
Seriously, even something insane like sh -c 'echo "\"un'\''kno\"wn\$\$\$'\'' with \$\"\$\$. \"zzz\""'
gets properly tokenized into [bash, -c, echo "\"un'kno\"wn\$\$\$' with \$\"\$\$. \"zzz\""]
(By the way, when run, this command outputs "un'kno"wn$$$' with $"$$. "zzz"
).
认真,甚至疯狂的喜欢的东西sh - c“回声”\“联合国”\“kno \”wn \ \ \ $美元的\“美元\ \ \ \ $美元。\“zzz \”“得到妥善标记为[bash - c,回声联合国'kno \“\ wn \ \ \ $美元”\ \“\ \ $美元。(顺便说一下,在运行时,这个命令输出的是“un'kno”,“$$$ $”。“打鼾声”)。
#4
1
/**
* [code borrowed from ant.jar]
* Crack a command line.
* @param toProcess the command line to process.
* @return the command line broken into strings.
* An empty or null toProcess parameter results in a zero sized array.
*/
public static String[] translateCommandline(String toProcess) {
if (toProcess == null || toProcess.length() == 0) {
//no command? no string
return new String[0];
}
// parse with a simple finite state machine
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
final ArrayList<String> result = new ArrayList<String>();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
while (tok.hasMoreTokens()) {
String nextTok = tok.nextToken();
switch (state) {
case inQuote:
if ("\'".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
case inDoubleQuote:
if ("\"".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
default:
if ("\'".equals(nextTok)) {
state = inQuote;
} else if ("\"".equals(nextTok)) {
state = inDoubleQuote;
} else if (" ".equals(nextTok)) {
if (lastTokenHasBeenQuoted || current.length() != 0) {
result.add(current.toString());
current.setLength(0);
}
} else {
current.append(nextTok);
}
lastTokenHasBeenQuoted = false;
break;
}
}
if (lastTokenHasBeenQuoted || current.length() != 0) {
result.add(current.toString());
}
if (state == inQuote || state == inDoubleQuote) {
throw new RuntimeException("unbalanced quotes in " + toProcess);
}
return result.toArray(new String[result.size()]);
}
#5
1
Expanding on Andreas_D's answer, instead of copying, use CommandLineUtils.translateCommandline(String toProcess)
from the excellent Plexus Common Utilities library.
扩展Andreas_D的答案,而不是复制,使用CommandLineUtils。从优秀的丛式公用事业库中翻译过来的。
#1
23
Here is a pretty easy alternative for splitting a text line from a file into an argument vector so that you can feed it into your options parser:
这里有一个很简单的方法,可以将文本行从文件分割成一个参数向量,这样您就可以将它输入到您的选项解析器中:
This is the solution:
这是解决方案:
public static void main(String[] args) {
String myArgs[] = Commandline.translateCommandline("-a hello -b world -c \"Hello world\"");
for (String arg:myArgs)
System.out.println(arg);
}
The magic class Commandline
is part of ant. So you either have to put ant on the classpath or just take the Commandline class as the used method is static.
魔法类命令行是ant的一部分。所以你要么把ant放在类路径上,要么把Commandline类作为使用的方法是静态的。
#2
9
You should use a fully featured modern object oriented Command Line Argument Parser I suggest my favorite Java Simple Argument Parser. And how to use JSAP, this is using Groovy as an example, but it is the same for straight Java. There is also args4j which is in some ways more modern than JSAP because it uses annotations, stay away from the apache.commons.cli stuff, it is old and busted and very procedural and un-Java-eques in its API. But I still fall back on JSAP because it is so easy to build your own custom argument handlers.
您应该使用一个功能齐全的现代面向对象的命令行参数解析器,我建议使用我最喜欢的Java简单参数解析器。以及如何使用JSAP,这是用Groovy作为示例,但对于纯Java也是如此。还有args4j,它在某些方面比JSAP更现代,因为它使用注释,远离apache.common .cli之类的东西,它在API中是陈旧的、被破坏的、非常程序化的和非java的。但是我仍然依赖于JSAP,因为构建自己的自定义参数处理程序非常容易。
There are lots of default Parsers for URLs, Numbers, InetAddress, Color, Date, File, Class, and it is super easy to add your own.
对于url、数字、InetAddress、颜色、日期、文件、类,有很多默认解析器,添加自己的内容非常简单。
For example here is a handler to map args to Enums:
例如,这里有一个处理args到Enums的处理程序:
import com.martiansoftware.jsap.ParseException;
import com.martiansoftware.jsap.PropertyStringParser;
/*
This is a StringParser implementation that maps a String to an Enum instance using Enum.valueOf()
*/
public class EnumStringParser extends PropertyStringParser
{
public Object parse(final String s) throws ParseException
{
try
{
final Class klass = Class.forName(super.getProperty("klass"));
return Enum.valueOf(klass, s.toUpperCase());
}
catch (ClassNotFoundException e)
{
throw new ParseException(super.getProperty("klass") + " could not be found on the classpath");
}
}
}
and I am not a fan of configuration programming via XML, but JSAP has a really nice way to declare options and settings outside your code, so your code isn't littered with hundreds of lines of setup that clutters and obscures the real functional code, see my link on how to use JSAP for an example, less code than any of the other libraries I have tried.
和我不是一个喜欢配置通过XML编程,但是JSAP有一个很好的方法声明以外的选项和设置您的代码,所以您的代码不是散落着数百行设置杂波和掩盖了真正的功能代码,看到我的链接如何使用JSAP为例,更少的代码比任何其他库我已经试过了。
This is a direction solution to your problem as clarified in your update, the lines in your "script" file are still command lines. Read them in from the file line by line and call JSAP.parse(String);
.
这是您的问题的一个方向解决方案,在您的更新中澄清,您的“脚本”文件中的行仍然是命令行。从文件中逐行读取它们,并调用JSAP.parse(String);
I use this technique to provide "command line" functionality to web apps all the time. One particular use was in a Massively Multiplayer Online Game with a Director/Flash front end that we enabled executing "commands" from the chat like and used JSAP on the back end to parse them and execute code based on what it parsed. Very much like what you are wanting to do, except you read the "commands" from a file instead of a socket. I would ditch joptsimple and just use JSAP, you will really get spoiled by its powerful extensibility.
我一直使用这种技术为web应用程序提供“命令行”功能。一个特别的用途是在一个大型多人在线游戏中使用一个Director/Flash前端,我们可以从聊天中执行“命令”,然后在后台使用JSAP来解析它们,并根据解析的内容执行代码。很像你想要做的,除非你从一个文件而不是一个套接字读取“命令”。我会抛弃joptsimple,只使用JSAP,你会被它强大的可扩展性所破坏。
#3
8
If you need to support only UNIX-like OSes, there is an even better solution. Unlike Commandline
from ant, ArgumentTokenizer from DrJava is more sh
-like: it supports escapes!
如果您只需要支持类unix的OSes,那么还有一个更好的解决方案。不同于ant的命令行,来自DrJava的ArgumentTokenizer更像sh:它支持转义!
Seriously, even something insane like sh -c 'echo "\"un'\''kno\"wn\$\$\$'\'' with \$\"\$\$. \"zzz\""'
gets properly tokenized into [bash, -c, echo "\"un'kno\"wn\$\$\$' with \$\"\$\$. \"zzz\""]
(By the way, when run, this command outputs "un'kno"wn$$$' with $"$$. "zzz"
).
认真,甚至疯狂的喜欢的东西sh - c“回声”\“联合国”\“kno \”wn \ \ \ $美元的\“美元\ \ \ \ $美元。\“zzz \”“得到妥善标记为[bash - c,回声联合国'kno \“\ wn \ \ \ $美元”\ \“\ \ $美元。(顺便说一下,在运行时,这个命令输出的是“un'kno”,“$$$ $”。“打鼾声”)。
#4
1
/**
* [code borrowed from ant.jar]
* Crack a command line.
* @param toProcess the command line to process.
* @return the command line broken into strings.
* An empty or null toProcess parameter results in a zero sized array.
*/
public static String[] translateCommandline(String toProcess) {
if (toProcess == null || toProcess.length() == 0) {
//no command? no string
return new String[0];
}
// parse with a simple finite state machine
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
final ArrayList<String> result = new ArrayList<String>();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
while (tok.hasMoreTokens()) {
String nextTok = tok.nextToken();
switch (state) {
case inQuote:
if ("\'".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
case inDoubleQuote:
if ("\"".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
default:
if ("\'".equals(nextTok)) {
state = inQuote;
} else if ("\"".equals(nextTok)) {
state = inDoubleQuote;
} else if (" ".equals(nextTok)) {
if (lastTokenHasBeenQuoted || current.length() != 0) {
result.add(current.toString());
current.setLength(0);
}
} else {
current.append(nextTok);
}
lastTokenHasBeenQuoted = false;
break;
}
}
if (lastTokenHasBeenQuoted || current.length() != 0) {
result.add(current.toString());
}
if (state == inQuote || state == inDoubleQuote) {
throw new RuntimeException("unbalanced quotes in " + toProcess);
}
return result.toArray(new String[result.size()]);
}
#5
1
Expanding on Andreas_D's answer, instead of copying, use CommandLineUtils.translateCommandline(String toProcess)
from the excellent Plexus Common Utilities library.
扩展Andreas_D的答案,而不是复制,使用CommandLineUtils。从优秀的丛式公用事业库中翻译过来的。