基本上,我正在传递一个字符串,我需要以与命令行选项由* nix shell标记的方式相同的方式对其进行标记
说我有以下字符串
"Hello\" World" "Hello Universe" Hi
我怎么能把它变成3元素列表
以下是我的第一次尝试,但它有很多问题
代码:
public void test() {
String str = "\"Hello\\\" World\" \"Hello Universe\" Hi";
List<String> list = split(str);
}
public static List<String> split(String str) {
Pattern pattern = Pattern.compile(
"\"[^\"]*\"" + /* double quoted token*/
"|'[^']*'" + /*single quoted token*/
"|[A-Za-z']+" /*everything else*/
);
List<String> opts = new ArrayList<String>();
Scanner scanner = new Scanner(str).useDelimiter(pattern);
String token;
while ((token = scanner.findInLine(pattern)) != null) {
opts.add(token);
}
return opts;
}
因此以下代码的错误输出是
编辑我对非正则表达式解决方案完全开放。这只是我想到的第一个解决方案
答案 0 :(得分:2)
我很确定你不能通过标记正则表达式来做到这一点。如果需要处理嵌套和转义分隔符,则需要编写解析器。参见例如http://kore-nordmann.de/blog/do_NOT_parse_using_regexp.html
虽然我不知道,但是会有开源解析器可以做你想做的事情。您还应该查看StreamTokenizer类。
答案 1 :(得分:2)
如果您决定放弃正则表达式,并进行解析,则有几种选择。如果您愿意仅使用双引号或单引号(但不是两者)作为引用,那么您可以使用StreamTokenizer轻松解决此问题:
public static List<String> tokenize(String s) throws IOException {
List<String> opts = new ArrayList<String>();
StreamTokenizer st = new StreamTokenizer(new StringReader(s));
st.quoteChar('\"');
while (st.nextToken() != StreamTokenizer.TT_EOF) {
opts.add(st.sval);
}
return opts;
}
如果你必须同时支持两个引号,这里有一个天真的实现应该有效(告诫像''blah \“blah”blah'这样的字符串会产生类似'blah“blahblah'的东西。如果不行,你需要做一些改变):
public static List<String> splitSSV(String in) throws IOException {
ArrayList<String> out = new ArrayList<String>();
StringReader r = new StringReader(in);
StringBuilder b = new StringBuilder();
int inQuote = -1;
boolean escape = false;
int c;
// read each character
while ((c = r.read()) != -1) {
if (escape) { // if the previous char is escape, add the current char
b.append((char)c);
escape = false;
continue;
}
switch (c) {
case '\\': // deal with escape char
escape = true;
break;
case '\"':
case '\'': // deal with quote chars
if (c == '\"' || c == '\'') {
if (inQuote == -1) { // not in a quote
inQuote = c; // now we are
} else {
inQuote = -1; // we were in a quote and now we aren't
}
}
break;
case ' ':
if (inQuote == -1) { // if we aren't in a quote, then add token to list
out.add(b.toString());
b.setLength(0);
} else {
b.append((char)c); // else append space to current token
}
break;
default:
b.append((char)c); // append all other chars to current token
}
}
if (b.length() > 0) {
out.add(b.toString()); // add final token to list
}
return out;
}
答案 2 :(得分:2)
总结一下,你想要在空白上拆分,除非用双引号括起来,前面没有反斜杠。
/([ \t]+)|(\\")|(")|([^ \t"]+)/
这为您提供了一系列SPACE,ESCAPED_QUOTE,QUOTE和TEXT标记。
答案 3 :(得分:1)
我想如果你使用这样的模式:
Pattern pattern = Pattern.compile("\".*?(?<!\\\\)\"|'.*?(?<!\\\\)'|[A-Za-z']+");
然后它会给你想要的输出。当我使用您的输入数据运行时,我得到了这个列表:
["Hello\" World", "Hello Universe", Hi]
我在您自己的问题中使用了[A-Za-z']+
但不应该只是:[A-Za-z]+
将您的opts.add(token);
行更改为:
opts.add(token.replaceAll("^\"|\"$|^'|'$", ""));
答案 4 :(得分:0)
你需要做的第一件事是停止以split()
的方式思考这项工作。 split()
用于分解this/that/the other
之类的简单字符串,其中/
始终是分隔符。但你试图分裂空白,除非空格在引号内,除了如果引号用反斜杠转义(如果反斜杠转义引号,它们可能会逃避其他事物,像其他反斜杠一样)。
除了所有异常例外之外,创建正则表达式以匹配所有可能的分隔符是不可能的,甚至不能使用像外观,条件,不情愿和占有量词等花哨的噱头。你想要做的是匹配令牌,而不是分隔符。
在下面的代码中,用双引号或单引号括起来的标记可能包含空格以及引号字符(如果前面带有反斜杠)。除了封闭引号之外的所有内容都在第1组(对于双引号)或第2组(单引号)中捕获。即使在非引用的标记中,任何字符都可以使用反斜杠进行转义;在单独的步骤中删除“转义”反斜杠。
public static void test()
{
String str = "\"Hello\\\" World\" 'Hello Universe' Hi";
List<String> commands = parseCommands(str);
for (String s : commands)
{
System.out.println(s);
}
}
public static List<String> parseCommands(String s)
{
String rgx = "\"((?:[^\"\\\\]++|\\\\.)*+)\"" // double-quoted
+ "|'((?:[^'\\\\]++|\\\\.)*+)'" // single-quoted
+ "|\\S+"; // not quoted
Pattern p = Pattern.compile(rgx);
Matcher m = p.matcher(s);
List<String> commands = new ArrayList<String>();
while (m.find())
{
String cmd = m.start(1) != -1 ? m.group(1) // strip double-quotes
: m.start(2) != -1 ? m.group(2) // strip single-quotes
: m.group();
cmd = cmd.replaceAll("\\\\(.)", "$1"); // remove escape characters
commands.add(cmd);
}
return commands;
}
输出:
Hello" World Hello Universe Hi
这与基于正则表达式的解决方案一样简单 - 它并不真正处理格式错误的输入,如不平衡的报价。如果您不熟悉正则表达式,那么使用纯手工编码的解决方案或者更好的是专用的命令行解释器(CLI)库可能会更好。