在Clojure中从字符串解析命令行参数

6
我处于需要解析字符串中的参数的情况,这些参数与提供给Java/Clojure应用程序的命令行参数解析方式相同。
例如,我需要将"foo \"bar baz\" 'fooy barish' foo"转换为("foo" "bar baz" "fooy barish" "foo")
我想知道是否有一种方法可以使用Java或Clojure使用的解析器来完成此操作。虽然我不排斥使用正则表达式,但是我对正则表达式的掌握不够熟练,如果要尝试编写一个用于此目的的正则表达式,我肯定会失败。
有任何想法吗?

我认为你的 shell 负责分割命令行参数,而不是 Java。 - Brian Carper
1
无论如何,我仍在寻找一个合适的方法来完成这个任务。 - Rayne
4个回答

4

更新了一个更加复杂的版本。这已经是荒谬了;下一个版本将使用适当的解析器(或c.c.monads和一点类似Parsec的逻辑)。请参考此答案的修订历史记录以获取原始内容。

这一堆复杂的函数似乎能够完成任务(对于这个问题,我不是很DRY,抱歉!):

(defn initial-state [input]
  {:expecting nil
   :blocks (mapcat #(str/split % #"(?<=\s)|(?=\s)")
                   (str/split input #"(?<=(?:'|\"|\\))|(?=(?:'|\"|\\))"))
   :arg-blocks []})

(defn arg-parser-step [s]
  (if-let [bs (seq (:blocks s))]
    (if-let [d (:expecting s)]
      (loop [bs bs]
        (cond (= (first bs) d)
              [nil (-> s
                       (assoc-in [:expecting] nil)
                       (update-in [:blocks] next))]
              (= (first bs) "\\")
              [nil (-> s
                       (update-in [:blocks] nnext)
                       (update-in [:arg-blocks]
                                  #(conj (pop %)
                                         (conj (peek %) (second bs)))))]
              :else
              [nil (-> s
                       (update-in [:blocks] next)
                       (update-in [:arg-blocks]
                                  #(conj (pop %) (conj (peek %) (first bs)))))]))
      (cond (#{"\"" "'"} (first bs))
            [nil (-> s
                     (assoc-in [:expecting] (first bs))
                     (update-in [:blocks] next)
                     (update-in [:arg-blocks] conj []))]
            (str/blank? (first bs))
            [nil (-> s (update-in [:blocks] next))]
            :else
            [nil (-> s
                     (update-in [:blocks] next)
                     (update-in [:arg-blocks] conj [(.trim (first bs))]))]))
    [(->> (:arg-blocks s)
          (map (partial apply str)))
     nil]))

(defn split-args [input]
  (loop [s (initial-state input)]
    (let [[result new-s] (arg-parser-step s)]
      (if result result (recur new-s)))))

值得鼓励的是,以下代码返回true

(= (split-args "asdf 'asdf \" asdf' \"asdf ' asdf\" asdf")
   '("asdf" "asdf \" asdf" "asdf ' asdf" "asdf"))

那么这样做:
(= (split-args "asdf asdf '  asdf \" asdf ' \" foo bar ' baz \" \" foo bar \\\" baz \"")
   '("asdf" "asdf" "  asdf \" asdf " " foo bar ' baz " " foo bar \" baz "))

希望这将修剪常规参数,但不包含引号的参数,处理双引号和单引号(包括未引用的双引号内的引用双引号),注意它当前以相同方式处理未引用单引号内的引用单引号,这显然与*nix shell不一致...啊。请注意,它基本上是一个自适应状态单子中的计算,只是以一种特别丑陋的方式编写,并急需DRYing。:-P

天啊,我真的很害怕我必须把那个东西放进我的代码里。这应该比实际情况容易得多。:\非常感谢!:D - Rayne
1
你知道吗,也许你应该考虑将这个放入contrib或一个小型库中。说真的,这可能对不止我有用。 - Rayne
啊,好的,我马上修复。(可能会让它更加DRY一些。) - Michał Marczyk
这很容易解决 - 用(mapcat #(str/split % #"(?<=\s)|(?=\s)") ...)包装str/split表单即可。然而,我发现了另一个与引号转义有关的错误...一旦我修复了它,就会发布更新版本。 - Michał Marczyk
来自未来的问候。希望你不介意我把这个复杂的代码片段变成了一个可重用的库:https://github.com/daveyarwood/str-to-argv 。也许在2015年有更好的方法,比如使用Instaparse。如果有的话,我会很乐意更新我的库。无论如何,这个库运行得非常好。感谢@MichałMarczyk! - Dave Yarwood
显示剩余7条评论

2

这让我很困扰,所以我在ANTLR中使它工作了。下面的语法应该可以给你一个实现它的想法。它包括对反斜杠转义序列的基本支持。

在Clojure中使用ANTLR太复杂了,无法在此文本框中写出来。不过我写了一篇关于此的博客文章

grammar Cmd;

options {
    output=AST;
    ASTLabelType=CommonTree;
}

tokens {
    DQ = '"';
    SQ = '\'';
    BS = '\\';
}

@lexer::members {
    String strip(String s) {
        return s.substring(1, s.length() - 1);
    }
}

args: arg (sep! arg)* ;
arg : BAREARG
    | DQARG 
    | SQARG
    ;
sep :   WS+ ;

DQARG  : DQ (BS . | ~(BS | DQ))+ DQ
        {setText( strip(getText()) );};
SQARG  : SQ (BS . | ~(BS | SQ))+ SQ
        {setText( strip(getText()) );} ;
BAREARG: (BS . | ~(BS | WS | DQ | SQ))+ ;

WS  :   ( ' ' | '\t' | '\r' | '\n');

0
我最终做了这件事:
(filter seq
        (flatten
         (map #(%1 %2)
              (cycle [#(s/split % #" ") identity])
              (s/split (read-line) #"(?<!\\)(?:'|\")"))))

恐怕这会与 'asdf"asdf' 这样的内容不兼容。 - Michał Marczyk
此外,反斜杠本身也可以被转义...只是指出一些问题,以防您想要修复它们,如果我找到替代解决方案,我会将其发布为答案。 - Michał Marczyk
的确。我知道那不是完全正确的,但当时我只能接受任何可得之物。 - Rayne

0

我知道这是一个非常古老的帖子,但我遇到了同样的问题,并使用Java互操作性进行调用:

(CommandLineUtils/translateCommandline cmd-line)

来自Plexus Common Utilities


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