在Clojure中构建CLI脚本的常见/标准方法是什么?
在我看来,这种方法应该包括以下特征:
-
一种轻松处理争论的方法,stdin/out/err。
-
无需花费太多时间来启动(理想情况下具有某种 JIT),否则就会失去在 shell 中将东西一起破解的目的。
-
此外,期望一种简单的方法可以在不设置项目(可能全局安装它们)的情况下包含一次性依赖项,这是合理的。
理想情况下,提供解决方案用法的简单示例将不胜感激。有点等同于:
#!/bin/bash
echo "$@"
cat /dev/stdin
注意:我知道这个问题之前在这里受到过一些质疑。但这个问题是不完整的,答案也没有达成共识,似乎不存在很大一部分解决方案。
现在有了新的CLI工具,可以在不使用第三方工具的情况下创建一个独立的Clojure脚本。安装clj
命令行工具后,像下面这样的脚本应该就可以工作了。
就原始问题而言,这可以与任何Clojure/JVM CLI程序一样好,具体取决于您:require
的库。我还没有对它进行基准测试,所以我不会评论性能,但如果它让你担心,那么请自己试验一下,看看启动时间是否可以接受。我想说这在依赖管理方面得分很高,因为脚本是完全独立的(除了现在无论如何都推荐运行 Clojure 的clj
工具)。
文件:~/bin/script.sh
#!/bin/sh
"exec" "clj" "-Sdeps" "{:deps,{hiccup,{:mvn/version,"1.0.5"}}}" "$0" "$@"
(ns my-script
(:require
[hiccup.core :as hiccup]))
(println
(hiccup/html
[:div
[:span "Command line args: " (clojure.string/join ", " *command-line-args*)]
[:span "Stdin: " (read-line)]]))
然后确保它是可执行的:
$ chmod +x ~/bin/script.sh
并运行它:
$ echo "stdin" | script.sh command line args
<div><span>Command line args: command, line, args</span><span>Stdin: stdin</span></div>
铌。这主要是一个 shell 脚本,它将第三行的字符串视为要执行的命令。随后的执行将使用给定的参数运行clj
命令行工具,该工具将这些字符串评估为字符串(没有副作用),然后继续计算下面的 Clojure 代码。
另请注意,依赖项被指定为传递给第三行clj
的映射。你可以在Clojure网站上阅读更多关于它是如何工作的。依赖关系图中的标记用逗号分隔,Clojure 将其视为空格,但大多数 shell 不会将其视为空格。
感谢"clojurians"Slack小组 #tools-deps频道上的好人,这个解决方案从何而来。
一个选项是运行在MacOS和Linux上的Planck。它使用自托管的ClojureScript,具有快速启动并针对JavaScriptCore。
它有一个很好的SDK,并模仿了Clojure的一些东西,而这些东西在ClojureScript中没有,例如planck.io
类似于clojure.java.io
.它支持通过tools.deps.alpha
/deps.edn
加载依赖项。
回显stdin
非常简单:
(require '[planck.core :refer [*in* slurp]])
(print (slurp *in*))
并打印命令行参数:
(println *command-line-args*)
。
$ echo "foo" | planck stdin.cljs 1 2 3
foo
(1 2 3)
一个具有依赖项的独立脚本示例,即不是项目:Planck 中的tree
命令行工具。
需要注意的是,Planck 不支持使用 npm 依赖项。因此,如果您需要这些,请选择针对NodeJS的Lumo。
第三种选择是小丑,它是用 Go 编写的 Clojure 解释器。
我知道您要求非项目创建方法来实现这一目标,但由于这个特定问题已经在我脑海中存在了很长一段时间,我想我会提出另一种选择。
TLDR:跳转到下面的"创建可执行 CLI 命令"部分
背景
我有与您前段时间几乎相同的要求列表,并登陆创建可执行 jar 文件。我不是在谈论通过java -jar myfile.jar
执行,而是自包含的uber-jars,您可以像使用任何其他二进制文件一样直接执行。
如果您阅读 zip 文件规范(jar 文件所遵循的 jar 文件是 zip 文件),事实证明这实际上是可能的。简短的版本是您需要:
- 用你需要的东西做一个胖罐 子
- 在文件开头的二进制 jar 内容中插入 bash/bat/shell 脚本
chmod +x
uber jar 文件(或者如果在 Windows 上,请选中可执行文件框)- 重写 JAR 文件元数据记录,以便插入的脚本文本不会使 zip 文件内部偏移失效
应该注意的是,这实际上是由 zip 文件规范支持的。这就是自解压 zip 文件等的工作方式,生成的胖 jar(在上述过程之后)仍然是有效的 jar 文件和有效的 zip 存档。所有相关命令(如java -jar
)仍然有效,文件现在也可以直接从命令行执行。
此外,按照上述模式,还可以添加对滴灌jvm启动器之类的支持,这大大加快了cli脚本的启动时间。
事实证明,当我大约一年前开始研究这个问题时,重写 jar 文件元数据的最后一点的库并不存在。不仅在 clojure 中,而且在整个 JVM 上。这仍然让我大吃一惊:jvm 上所有语言的中央部署单元是 jar 文件,并且没有实际读取 jar 文件内部的库。内部就像实际的zip文件结构一样,而不仅仅是java的ZipFile和朋友所做的。
此外,我找不到一个用于clojure的库,它以干净的方式处理zip文件规范所需的二进制结构。
溶液:
- octet 具有我认为可用于 clojure 的二进制库中最干净的接口,因此我为 octet 编写了一个拉取请求,添加了对 zip 文件规范所需功能的支持。
- 然后,我创建了一个新的库 clj-zip-meta,它读取和解释 zip 文件元数据,并且能够进行上面最后一点中描述的偏移量重写。
- 然后,我创建了一个对现有 clojure lib lein-binplus 的拉取请求,以添加对 clj-zip-meta 实现的 zip 元重写的支持,并添加对自定义前导码脚本的支持,以便能够在不需要
java -jar
的情况下创建真正的可执行 jar
。 - 在所有这些之后,我创建了一个leiningen模板cli-cmd来支持创建cli命令项目,该项目支持上述所有花里胡哨的功能,并具有结构良好的命令行解析设置...或者我认为结构良好的:)。欢迎评论。
创建可执行 CLI 命令
因此,您可以使用leiningen创建一个新的命令行clojure应用程序,并使用以下命令运行它:
~> lein new cli-cmd mycmd
~> cd mycmd
~> lein bin
Compiling mycmd.core
Compiling mycmd.core
Created /home/mbjarland/tmp/clj-cmd/mycmd/target/mycmd-0.1.0-SNAPSHOT.jar
Created /home/mbjarland/tmp/clj-cmd/mycmd/target/mycmd-0.1.0-SNAPSHOT-standalone.jar
Creating standalone executable: /home/mbjarland/tmp/clj-cmd/mycmd/target/mycmd
Re-aligning zip offsets
~> target/mycmd
---- debug output, remove for production code ----
options {:port 80, :hostname "localhost", :verbosity 0}
arguments []
errors nil
summary
-p, --port PORT 80 Port number
-H, --hostname HOST localhost Remote host
--detach Detach from controlling process
-v Verbosity level; may be specified multiple times to increase value
-h, --help
--------------------------------------------------
This is my program. There are many like it, but this one is mine.
Usage: mycmd [options] action
Options:
-p, --port PORT 80 Port number
-H, --hostname HOST localhost Remote host
--detach Detach from controlling process
-v Verbosity level; may be specified multiple times to increase value
-h, --help
Actions:
start Start a new server
stop Stop an existing server
status Print a server's status
Please refer to the manual page for more information.
Error: invalid action '' specified!
命令的输出只是我添加到 leiningen 模板的样板示例命令行分析。
自定义前导码脚本位于boot/jar-preamble.sh
,它支持滴灌。换句话说,如果你的路径上有滴水,生成的可执行文件会使用它,否则它会回退到内部启动 uber jar 的标准java -jar
方式。
命令行解析的源代码和 cli 应用程序的代码正常存在于 src 目录下。
如果您想进行黑客攻击,可以更改前导码脚本并重新运行lein bin
新的前导码将通过构建过程插入到可执行文件中。
另外应该注意的是,这种方法仍然java -jar
幕后,所以你确实需要java在你的路径上。
Ayway,冗长的解释,但希望它对有这个问题的人有一些用处。
考虑Lumo,一个专门为脚本设计的ClojureScript环境。
请注意,虽然它同时支持 ClojureScript (JAR) 和 NPM 依赖项,但依赖项支持仍在开发中。
我编写了许多 Clojure (JVM) 脚本,并使用 CLI-matic 库 https://github.com/l3nz/cli-matic/来抽象大部分与命令行解析、创建和维护帮助、错误等相关的样板文件。