[英]Parsing command-line arguments from a STRING in Clojure
我处于一种情况,我需要以与在命令行上提供给 Java/Clojure 应用程序时解析字符串的方式相同的方式解析字符串中的参数。
例如,我需要将"foo \\"bar baz\\" 'fooy barish' foo"
变成("foo" "bar baz" "fooy barish" "foo")
。
我很好奇是否有办法使用 Java 或 Clojure 用来执行此操作的解析器。 我不反对使用正则表达式,但我很讨厌正则表达式,如果我试图为此编写一个,我会很失败。
有任何想法吗?
更新了一个新的,更复杂的版本。 这在官方上是荒谬的; 下一次迭代将使用适当的解析器(或 ccmonads 和一些类似 Parsec 的逻辑)。 请参阅此答案的修订历史以获取原件。
这一堆令人费解的函数似乎可以解决问题(不是我最干的,抱歉!):
(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 方式的差异...... argh) 等等。请注意,它基本上是一个临时状态 monad 中的计算,只是以一种特别丑陋的方式编写,并且迫切需要干燥。 :-P
这让我很烦恼,所以我让它在 ANTLR 中工作。 下面的语法应该让你知道如何去做。 它包括对反斜杠转义序列的基本支持。
让 ANTLR 在 Clojure 中工作太多了,无法在此文本框中写入。 我写了一篇关于它的博客文章。
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');
我最终这样做了:
(filter seq
(flatten
(map #(%1 %2)
(cycle [#(s/split % #" ") identity])
(s/split (read-line) #"(?<!\\)(?:'|\")"))))
我知道这是一个非常古老的线程,但我遇到了同样的问题并使用 java interop 调用:
(CommandLineUtils/translateCommandline cmd-line)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.