Java的shlex替代方案

15

是否有Java的shlex替代品? 我想能够像shell处理它们一样拆分引号分隔的字符串。例如,如果我发送:

one two "three four"
并执行拆分操作,我希望收到标记
one
two
three four


值得注意的是,“像shell一样处理它们”是一个相当困难的任务;shlex做得很好,但许多天真的算法不会。例如,在shell中,“three four”和“three”' 'four是完全等价的,而three\ four也是如此。 - Charles Duffy
3个回答

10

今天我遇到了一个类似的问题,看起来像StringTokenizer、StrTokenizer、Scanner这些标准选项都不太适合。不过,实现基本功能并不难。

这个例子处理了其他答案中目前被评论提及的所有边缘情况。请注意,我尚未检查其是否完全符合POSIX标准。包括单元测试的Gist可以在GitHub上获取——通过unlicense发布到公共领域。

public List<String> shellSplit(CharSequence string) {
    List<String> tokens = new ArrayList<String>();
    boolean escaping = false;
    char quoteChar = ' ';
    boolean quoting = false;
    int lastCloseQuoteIndex = Integer.MIN_VALUE;
    StringBuilder current = new StringBuilder();
    for (int i = 0; i<string.length(); i++) {
        char c = string.charAt(i);
        if (escaping) {
            current.append(c);
            escaping = false;
        } else if (c == '\\' && !(quoting && quoteChar == '\'')) {
            escaping = true;
        } else if (quoting && c == quoteChar) {
            quoting = false;
            lastCloseQuoteIndex = i;
        } else if (!quoting && (c == '\'' || c == '"')) {
            quoting = true;
            quoteChar = c;
        } else if (!quoting && Character.isWhitespace(c)) {
            if (current.length() > 0 || lastCloseQuoteIndex == (i - 1)) {
                tokens.add(current.toString());
                current = new StringBuilder();
            }
        } else {
            current.append(c);
        }
    }
    if (current.length() > 0 || lastCloseQuoteIndex == (string.length() - 1)) {
        tokens.add(current.toString());
    }

    return tokens;
}

你是否考虑给这个项目附加一个许可证(或明确地捐赠到公共领域)? - Charles Duffy
啊,这就是了,页面的最后一行:用户贡献在CC BY-SA 3.0许可下授权,需要署名。 - bukzor
@RayMyers:我们仍然需要知道这是否是您自己的工作,否则许可证是未知的。此外,CC-BY-SA许可证与Hadoop的Apache许可证不完全兼容(我需要使用它未经修改)。如果您将此代码专用于Unlicense,这些问题就会消失,否则我将不得不从头开始编写类似的代码。...我希望SO能更改他们的默认许可证。 - bukzor
@RayMyers:虽然这对我来说已经足够好了(谢谢!),但你应该知道,我经常看到的专家建议是,“公共领域”是一个非常脆弱的法律概念(例如,在美国之外甚至不存在),任何没有许可证的作品(包括那些“发布到公共领域”的作品)最好被认为是无许可证。最接近你想要做的许可证是Unlicense - bukzor
1
注意:此代码未正确处理带引号的空字符串。例如,输入"''"将被解析为空列表,而不是包含""的列表。 - j3h
显示剩余3条评论

6

看看Apache Commons Lang:

org.apache.commons.lang.text.StrTokenizer应该能够做到你想要的:

new StringTokenizer("one two \"three four\"", ' ', '"').getTokenArray();

2
不幸的是,与shlex不同,commons.lang不兼容POSIX。(-> (StrTokenizer. "\"foo\"'bar'baz") (.getTokenList))返回一个包含"foo"'bar'baz的单个条目,而不是(正确的)foobarbaz - Charles Duffy
@CharlesDuffy 你知道真正的答案吗? - bukzor
@bukzor,这就假定有一个工具。据我所知,目前还没有这样的工具,除非通过Jython从Java使用Python的shlex(可能,但需要引入相当大的依赖链)。 - Charles Duffy
尽管@RayMyers的答案看起来像一个可能的候选者。 - Charles Duffy

0

我使用以下Scala代码成功使用fastparse。 我不能保证它完整:

val kvParser = {
  import fastparse._
  import NoWhitespace._
  def nonQuoteChar[_:P] = P(CharPred(_ != '"'))
  def quotedQuote[_:P] = P("\\\"")
  def quotedElement[_:P] = P(nonQuoteChar | quotedQuote)
  def quotedContent[_:P] = P(quotedElement.rep)
  def quotedString[_:P] = P("\"" ~/ quotedContent.! ~ "\"")
  def alpha[_:P] = P(CharIn("a-zA-Z"))
  def digit[_:P] = P(CharIn("0-9"))
  def hyphen[_:P] = P("-")
  def underscore[_:P] = P("_")
  def bareStringChar[_:P] = P(alpha | digit | hyphen | underscore)
  def bareString[_:P] = P(bareStringChar.rep.!)
  def string[_:P] = P(quotedString | bareString)
  def kvPair[_:P] = P(string ~ "=" ~ string)
  def commaAndSpace[_:P] = P(CharIn(" \t\n\r").rep ~ "," ~ CharIn(" \t\n\r").rep)
  def kvPairList[_:P] = P(kvPair.rep(sep = commaAndSpace))
  def fullLang[_:P] = P(kvPairList ~ End)

  def res(str: String) = {
    parse(str, fullLang(_))
  }

  res _
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接