正则expression式麻烦,逃脱报价

基本上,我正在传递一个string,我需要以与命令行选项由* nix shell进行标记的方式大致相同的方式进行标记

说我有以下string

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

所以下面代码的错误输出是

  • “你好\”
  • 世界
  • “”
  • 你好
  • 宇宙
  • 你好

编辑我完全打开一个非正则expression式的解决scheme。 这只是第一个想到的解决scheme

如果你决定放弃正则表达式,而不是解析,有几个选项。 如果您愿意只有双引号或单引号(但不能同时引用),那么您可以使用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”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; } 

我很确定你不能通过在正则表达式上进行标记来做到这一点。 如果你需要处理嵌套和转义的分隔符,你需要编写一个分析器。 参见例如http://kore-nordmann.de/blog/do_NOT_parse_using_regexp.html

将有开源的解析器,可以做你想做的,虽然我不知道。 你也应该看看StreamTokenizer类。

总结一下,除了用双引号括起来之外,你还想分割空白,除了前面加反斜杠。

步骤1:标记输入: /([ \t]+)|(\\")|(")|([^ \t"]+)/

这给你一个空格,ESCAPED_QUOTE,QUOTE和TEXT标记的序列。

第二步:建立一个有限状态机,对令牌进行匹配和反应:

状态:开始

  • 空格 – >返回空字符串
  • ESCAPED_QUOTE – >错误(?)
  • QUOTE – >状态:= WITHIN_QUOTES
  • TEXT – >返回文本

状态:WITHIN_QUOTES

  • 空格 – >为累加器增加值
  • ESCAPED_QUOTE – >向累加器添加报价
  • QUOTE – >返回并清除累加器; 状态:=开始
  • 文本 – >添加文本到累加器

第3步:利润!

我想如果你使用这样的模式:

 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("^\"|\"$|^'|'$", "")); 

你需要做的第一件事就是不要用split()来思考这个工作。 split()是用来分解简单的字符串,比如this/that/the other ,其中/始终是一个分隔符。 但是你试图分割空白, 除非空格在引号内, 除非引号用反斜杠转义(如果反斜杠转义引号,它们可能会像其他反斜杠一样转义)。

除了所有这些例外情况外,创建一个正则表达式来匹配所有可能的分隔符是不可能的,即使是像lookaround,conditionals,勉强和占有量词这样的花哨的噱头也是如此。 你想要做的是匹配令牌 ,而不是分隔符。

在下面的代码中,用双引号或单引号括起来的标记可能包含空格以及引号字符(如果前面带有反斜杠)。 除了包含引号的所有内容都被捕获到组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; } 

输出:

你好,世界
宇宙你好
你好 

这与基于正则表达式的解决方案一样简单 – 它并不真正处理格式错误的输入,例如不平衡的引号。 如果您不熟练使用正则表达式,那么使用纯手工编码解决方案或更好的方法是使用专用的命令行解释器(CLI)库,可能会更好。