对字符串进行标记但忽略引号内的分隔符

时间:2010-07-29 19:34:22

标签: java

我希望拥有以下字符串

!cmd 45 90 "An argument" Another AndAnother "Another one in quotes"

成为以下

的数组
{ "!cmd", "45", "90", "An argument", "Another", "AndAnother", "Another one in quotes" }

我试过

new StringTokenizer(cmd, "\"")

但是这会返回“Another”和“AndAnother as”另一个AndAnother“这不是预期的效果。

感谢。

编辑: 我再次改变了这个例子,这次我认为它解释了最好的情况,尽管它和第二个例子没什么不同。

13 个答案:

答案 0 :(得分:52)

在这种情况下,使用java.util.regex.Matcher并执行find()而不是任何类型的split要容易得多。

也就是说,不是在标记之间定义分隔符的模式,而是为标记本身定义模式。

以下是一个例子:

    String text = "1 2 \"333 4\" 55 6    \"77\" 8 999";
    // 1 2 "333 4" 55 6    "77" 8 999

    String regex = "\"([^\"]*)\"|(\\S+)";

    Matcher m = Pattern.compile(regex).matcher(text);
    while (m.find()) {
        if (m.group(1) != null) {
            System.out.println("Quoted [" + m.group(1) + "]");
        } else {
            System.out.println("Plain [" + m.group(2) + "]");
        }
    }

以上打印(as seen on ideone.com):

Plain [1]
Plain [2]
Quoted [333 4]
Plain [55]
Plain [6]
Quoted [77]
Plain [8]
Plain [999]

模式基本上是:

"([^"]*)"|(\S+)
 \_____/  \___/
    1       2

有两个替补:

  • 第一个备用匹配开头双引号,除了双引号(在第1组中捕获)之外的任何序列,然后是结束双引号
  • 第二个备用项匹配第2组中捕获的任何非空白字符序列
  • 此模式中替代项的顺序很重要

请注意,这不会处理引用段中的转义双引号。如果您需要这样做,那么模式会变得更复杂,但Matcher解决方案仍然有效。

参考

另见


附录

请注意,StringTokenizer遗留类。建议使用java.util.ScannerString.split,或者当然java.util.regex.Matcher,以获得最大的灵活性。

相关问题

答案 1 :(得分:6)

用老式的方式做。创建一个查看for循环中每个字符的函数。如果角色是一个空格,请将所有内容(不包括空格)取出并添加为数组的条目。注意位置,并再次执行相同的操作,将空格后的下一部分添加到数组中。遇到双引号时,将名为'inQuote'的布尔值标记为true,并在inQuote为true时忽略空格。当inQuote为true时命中引号时,将其标记为false并在遇到空格时返回到破坏状态。然后,您可以根据需要扩展它以支持转义字符等。

这可以用正则表达式完成吗?我想,我不知道。但是整个函数的写入时间比这个回复要少。

答案 2 :(得分:2)

以老式的方式:

public static String[] split(String str) {
    str += " "; // To detect last token when not quoted...
    ArrayList<String> strings = new ArrayList<String>();
    boolean inQuote = false;
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < str.length(); i++) {
        char c = str.charAt(i);
        if (c == '"' || c == ' ' && !inQuote) {
            if (c == '"')
                inQuote = !inQuote;
            if (!inQuote && sb.length() > 0) {
                strings.add(sb.toString());
                sb.delete(0, sb.length());
            }
        } else
            sb.append(c);
    }
    return strings.toArray(new String[strings.size()]);
}

我认为嵌套引号是非法的,并且也可以省略空标记。

答案 3 :(得分:2)

Apache Commons to rescue!

import org.apache.commons.text.StringTokenizer
import org.apache.commons.text.matcher.StringMatcher
import org.apache.commons.text.matcher.StringMatcherFactory
@Grab(group='org.apache.commons', module='commons-text', version='1.3')

def str = /is this   'completely "impossible"' or """slightly"" impossible" to parse?/

StringTokenizer st = new StringTokenizer( str )
StringMatcher sm = StringMatcherFactory.INSTANCE.quoteMatcher()
st.setQuoteMatcher( sm )

println st.tokenList

输出:

  

[是,完全&#34;不可能&#34;,或者,&#34;略微&#34;不可能,要解析?]

一些注意事项:

  1. 这是用Groovy编写的......它实际上是一个Groovy脚本。该 @Grab行提供了您需要的依赖关系类型的线索 (例如在build.gradle)...或者只包括.jar 当然是classpath
  2. StringTokenizer这里 java.util.StringTokenizer ...正如import行显示的那样 org.apache.commons.text.StringTokenizer
  3. def str = ... line是一种在Groovy中生成String的方法,其中包含两者 单引号和双引号,无需转入
  4. 在apache commons-text 1.3中可以找到
  5. StringMatcherFactory here:正如您所看到的,INSTANCE可以为您提供 一堆不同的StringMatcher。你甚至可以自己动手: 但您需要检查StringMatcherFactory源代码 看看它是如何完成的。
  6. YES!您不仅可以包含&#34;其他类型的报价&#34;并且它被正确地解释为不是令牌边界......但你甚至可以 逃避用于关闭令牌化的实际报价 ,通过将报价加倍String的令牌化保护位!尝试用几行代码来实现它......或者更确切地说,不要使用它!
  7. PS为什么使用Apache Commons比任何其他解决方案更好? 除了重新发明轮子这一事实之外,我至少可以想到两个原因:

    1. Apache工程师可以指望所有的陷阱,并开发出强大,全面测试,可靠的代码
    2. 这意味着你不会使用stoopid实用程序方法来破坏你漂亮的代码 - 你只需要一个漂亮,干净的代码就可以完全按照它在锡上所说的那样,让你继续使用,嗯,有趣的东西......
    3. PPS没有什么可以让你把Apache代码视为神秘的&#34;黑盒子&#34;。来源是开放的,通常完美地写入&#34;可访问&#34; Java的。因此,您可以自由地检查您的内容是如何完成的。这样做通常很有启发性。

      <强>

      对ArtB的问题充满好奇,我看了一眼来源:

      我们在StringMatcherFactory.java中看到

      private static final AbstractStringMatcher.CharSetMatcher QUOTE_MATCHER = new AbstractStringMatcher.CharSetMatcher(
                  "'\"".toCharArray());
      

      ......相当沉闷...

      所以这导致人们看看StringTokenizer.java:

      public StringTokenizer setQuoteMatcher(final StringMatcher quote) {
              if (quote != null) {
                  this.quoteMatcher = quote;
              }
              return this;
      }
      

      好的......然后,在同一个java文件中:

      private int readWithQuotes(final char[] srcChars ...
      

      包含评论:

      // If we've found a quote character, see if it's followed by a second quote. If so, then we need to actually put the quote character into the token rather than end the token.
      

      ......我无法再进一步了解这些线索。你可以选择:你的&#34; hackish&#34;解决方案,系统地预处理你的字符串,然后提交它们进行标记,将| \\\&#34; | s转换为| \&#34; \&#34; | s ...(即你替换每个字符串的地方) | \&#34; | with | &#34;&#34; |)...
      或者......你检查org.apache.commons.text.StringTokenizer.java以弄清楚如何调整代码。它是一个小文件。我不认为那会很困难。然后你编译,基本上是一个Apache代码的分支。

      我认为它无法配置。但是如果你找到了一个有意义的代码调整解决方案,你可以将它提交给Apache,然后它可能会被接受用于代码的下一次迭代,并且你的名字至少会出现在&#34;功能请求中。 Apache的一部分:这可能是kleos的一种形式,通过它你可以实现编程永生......

答案 4 :(得分:1)

最近遇到了类似的问题,其中必须将命令行参数忽略引号link进行拆分。

一种可能的情况:

  

“ / opt / jboss-eap / bin / jboss-cli.sh --connect --controller = localhost:9990 -c命令= \”部署/app/jboss-eap-7.1/standalone/updates/sample。战争--force \“”

这必须拆分为

/opt/jboss-eap/bin/jboss-cli.sh
--connect
--controller=localhost:9990
-c
command="deploy /app/jboss-eap-7.1/standalone/updates/sample.war --force"

只需添加到@polygenelubricants的答案中,在引号匹配器之前和之后都可以有任何非空格字符。

"\\S*\"([^\"]*)\"\\S*|(\\S+)"

示例:

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Tokenizer {

    public static void main(String[] args){

        String a = "/opt/jboss-eap/bin/jboss-cli.sh --connect --controller=localhost:9990 -c command=\"deploy " +
                "/app/jboss-eap-7.1/standalone/updates/sample.war --force\"";
        String b = "Hello \"Stack Overflow\"";
        String c = "cmd=\"abcd efgh ijkl mnop\" \"apple\" banana mango";
        String d = "abcd ef=\"ghij klmn\"op qrst";
        String e = "1 2 \"333 4\" 55 6    \"77\" 8 999";

        List<String> matchList = new ArrayList<String>();
        Pattern regex = Pattern.compile("\\S*\"([^\"]*)\"\\S*|(\\S+)");
        Matcher regexMatcher = regex.matcher(a);
        while (regexMatcher.find()) {
            matchList.add(regexMatcher.group());
        }
        System.out.println("matchList="+matchList);
    }
}

输出:

  

matchList = [/ opt / jboss-eap / bin / jboss-cli.sh,--connect,--controller = localhost:9990,-c,command =“ deploy /app/jboss-eap-7.1/standalone /updates/sample.war --force“]

答案 5 :(得分:0)

你在这里的例子只需要用双引号字符分开。

答案 6 :(得分:0)

这是一个老问题,但这是我作为有限状态机的解决方案。

高效,可预测且没有花哨的技巧。

100%的测试覆盖率。

拖放到您的代码中。

/**
 * Splits a command on whitespaces. Preserves whitespace in quotes. Trims excess whitespace between chunks. Supports quote
 * escape within quotes. Failed escape will preserve escape char.
 *
 * @return List of split commands
 */
static List<String> splitCommand(String inputString) {
    List<String> matchList = new LinkedList<>();
    LinkedList<Character> charList = inputString.chars()
            .mapToObj(i -> (char) i)
            .collect(Collectors.toCollection(LinkedList::new));

    // Finite-State Automaton for parsing.

    CommandSplitterState state = CommandSplitterState.BeginningChunk;
    LinkedList<Character> chunkBuffer = new LinkedList<>();

    for (Character currentChar : charList) {
        switch (state) {
            case BeginningChunk:
                switch (currentChar) {
                    case '"':
                        state = CommandSplitterState.ParsingQuote;
                        break;
                    case ' ':
                        break;
                    default:
                        state = CommandSplitterState.ParsingWord;
                        chunkBuffer.add(currentChar);
                }
                break;
            case ParsingWord:
                switch (currentChar) {
                    case ' ':
                        state = CommandSplitterState.BeginningChunk;
                        String newWord = chunkBuffer.stream().map(Object::toString).collect(Collectors.joining());
                        matchList.add(newWord);
                        chunkBuffer = new LinkedList<>();
                        break;
                    default:
                        chunkBuffer.add(currentChar);
                }
                break;
            case ParsingQuote:
                switch (currentChar) {
                    case '"':
                        state = CommandSplitterState.BeginningChunk;
                        String newWord = chunkBuffer.stream().map(Object::toString).collect(Collectors.joining());
                        matchList.add(newWord);
                        chunkBuffer = new LinkedList<>();
                        break;
                    case '\\':
                        state = CommandSplitterState.EscapeChar;
                        break;
                    default:
                        chunkBuffer.add(currentChar);
                }
                break;
            case EscapeChar:
                switch (currentChar) {
                    case '"': // Intentional fall through
                    case '\\':
                        state = CommandSplitterState.ParsingQuote;
                        chunkBuffer.add(currentChar);
                        break;
                    default:
                        state = CommandSplitterState.ParsingQuote;
                        chunkBuffer.add('\\');
                        chunkBuffer.add(currentChar);
                }
        }
    }

    if (state != CommandSplitterState.BeginningChunk) {
        String newWord = chunkBuffer.stream().map(Object::toString).collect(Collectors.joining());
        matchList.add(newWord);
    }
    return matchList;
}

private enum CommandSplitterState {
    BeginningChunk, ParsingWord, ParsingQuote, EscapeChar
}

答案 7 :(得分:0)

另一种老派的方式是:

public static void main(String[] args) {

    String text = "One two \"three four\" five \"six seven eight\" nine \"ten\"";
    String[] splits = text.split(" ");
    List<String> list = new ArrayList<>();
    String token = null;
    for(String s : splits) {

        if(s.startsWith("\"") ) {
            token = "" + s; 
        } else if (s.endsWith("\"")) {
            token = token + " "+ s;
            list.add(token);
            token = null;
        } else {
            if (token != null) {
                token = token + " " + s;
            } else {
                list.add(s);
            }
        }
    }
    System.out.println(list);
}

输出: - [一,二,&#34;三四&#34;,五,&#34;六七八&#34;,九]

答案 8 :(得分:0)

private static void findWords(String str) {
    boolean flag = false;
    StringBuilder sb = new StringBuilder();
    for(int i=0;i<str.length();i++) {
        if(str.charAt(i)!=' ' && str.charAt(i)!='"') {
            sb.append(str.charAt(i));
        }
        else {
            System.out.println(sb.toString());
            sb = new StringBuilder();
            if(str.charAt(i)==' ' && !flag)
                continue;
            else if(str.charAt(i)=='"') {
                if(!flag) {
                    flag=true;
                }
                i++;
                while(i<str.length() && str.charAt(i)!='"') {
                    sb.append(str.charAt(i));
                    i++;
                }
                flag=false;
                System.out.println(sb.toString());
                sb = new StringBuilder();
            }
        }
    }
}

答案 9 :(得分:0)

这就是我本人用于在命令行中拆分参数之类的东西。

它易于调整以用于多个定界符和引号,它可以处理单词内的引号(例如al' 'pha),它支持转义(引号和空格),而且非常宽大。

public final class StringUtilities {
    private static final List<Character> WORD_DELIMITERS = Arrays.asList(' ', '\t');
    private static final List<Character> QUOTE_CHARACTERS = Arrays.asList('"', '\'');
    private static final char ESCAPE_CHARACTER = '\\';

    private StringUtilities() {

    }

    public static String[] splitWords(String string) {
        StringBuilder wordBuilder = new StringBuilder();
        List<String> words = new ArrayList<>();
        char quote = 0;

        for (int i = 0; i < string.length(); i++) {
            char c = string.charAt(i);

            if (c == ESCAPE_CHARACTER && i + 1 < string.length()) {
                wordBuilder.append(string.charAt(++i));
            } else if (WORD_DELIMITERS.contains(c) && quote == 0) {
                words.add(wordBuilder.toString());
                wordBuilder.setLength(0);
            } else if (quote == 0 && QUOTE_CHARACTERS.contains(c)) {
                quote = c;
            } else if (quote == c) {
                quote = 0;
            } else {
                wordBuilder.append(c);
            }
        }

        if (wordBuilder.length() > 0) {
            words.add(wordBuilder.toString());
        }

        return words.toArray(new String[0]);
    }
}

答案 10 :(得分:-1)

试试这个:

String str = "One two \"three four\" five \"six seven eight\" nine \"ten\"";
String strArr[] = str.split("\"|\s");

这有点棘手,因为你需要逃避双引号。这个正则表达式应该使用空格(\ s)或双引号来标记字符串。

您应该使用String的split方法,因为它接受正则表达式,而StringTokenizer中的分隔符的构造函数参数不接受。在上面提供的内容结尾处,您可以添加以下内容:

String s;
for(String k : strArr) {
     s += k;
}
StringTokenizer strTok = new StringTokenizer(s);

答案 11 :(得分:-1)

试试这个:

String str = "One two \"three four\" five \"six seven eight\" nine \"ten\"";
String[] strings = str.split("[ ]?\"[ ]?");

答案 12 :(得分:-1)

我不知道你尝试做什么的上下文,但看起来你试图解析命令行参数。总的来说,这对于所有逃避问题都非常棘手;如果这是你的目标,我会亲自看看像JCommander这样的东西。