简体   繁体   English

在Clojure中构建CLI脚本

[英]Building CLI scripts in Clojure

What are the common/standard ways to build CLI scripts in Clojure? 在Clojure中构建CLI脚本的常用/标准方法是什么?

In my view such a method should include the following characteristics: 在我看来,这种方法应该包括以下特征:

  • A way of easily dealing with arguments, stdin/out/err. 一种轻松处理参数的方法,stdin / out / err。

  • Without taking too much to boot (ideally having some sort of JIT), otherwise one loses the purpose of hacking things together in one's shell. 没有花太多时间来启动(理想情况下有某种JIT),否则就会失去在一个人的shell中将事情混合在一起的目的。

  • Also it is reasonable to expect a easy way of including one time dependencies without setting up a project (maybe installing them globally). 此外,在没有设置项目(可能全局安装)的情况下,期望一种简单的方法来包含一次性依赖是合理的。

Ideally, providing a simple example of the solution usage would be much appreciated. 理想情况下,非常感谢提供解决方案使用的简单示例。 Somewhat equivalent to: 有点相当于:

#!/bin/bash
    echo "$@"
    cat /dev/stdin

Note: I'm aware that this question was somewhat questioned previously here . 注:我知道,这个问题是有些质疑以前这里 But the question is incomplete and the answers don't reach a consensus neither a significant proportion of the solutions that seems to exist. 但问题是不完整的,答案似乎没有达成共识,也没有达成共识,似乎不存在大部分解决方案。

An option would be Planck which runs on MacOS and Linux. 一个选项是Planck ,它运行在MacOS和Linux上。 It uses self-hosted ClojureScript, has fast startup and targets JavaScriptCore. 它使用自托管的ClojureScript,具有快速启动和目标JavaScriptCore。

It has a nice SDK and mimics some things from Clojure which you do not have in ClojureScript, eg planck.io resembles clojure.java.io . 它有一个很好的SDK并模仿Clojure中的一些你在ClojureScript中没有的东西,例如planck.io类似于clojure.java.io It supports loading dependencies via tools.deps.alpha / deps.edn . 它支持通过tools.deps.alpha / deps.edn加载依赖deps.edn

Echoing stdin is as easy as: 回应stdin非常简单:

(require '[planck.core :refer [*in* slurp]])
(print (slurp *in*))

and printing the command line arguments: 并打印命令行参数:

(println *command-line-args*)

... ...

$ echo "foo" | planck stdin.cljs 1 2 3
foo
(1 2 3)

An example of a standalone script, ie not a project, with dependencies: the tree command line tool in Planck . 具有依赖关系的独立脚本(即非项目)的示例: Planck中的tree命令行工具

One caveat is that Planck doesn't support using npm dependencies. 需要注意的是,普朗克不支持使用npm依赖项。 So if you need those, go for Lumo which targets NodeJS. 所以,如果你需要那些,请选择针对NodeJS的Lumo

A third option would be joker which is a Clojure interpreter written in Go. 第三种选择是joker ,它是用Go编写的Clojure解释器。

Now that there is new CLI tooling it is possible to create a standalone Clojure script without using third party tools. 现在有了新的CLI工具 ,可以在不使用第三方工具的情况下创建独立的Clojure脚本。 Once you've got the clj command line tool installed , a script like the one below should just work. 一旦安装clj命令行工具,就可以使用下面的脚本。

In terms of the original question, this can be as good as any Clojure/JVM CLI program at dealing with command line arguments and system input/output depending on what libraries you :require . 就原始问题而言,这可以与处理命令行参数和系统输入/输出的任何Clojure / JVM CLI程序一样好,具体取决于您:require I've haven't benchmarked it, so I won't comment on performance but if it worries you then please experiment yourself to see if startup time is acceptable to you. 我还没有对它进行基准测试,所以我不会对性能发表评论,但如果它让您担心,那么请亲自试验一下您是否可以接受启动时间。 I would say this scores highly on dependency management though, as the script is entirely standalone (apart from the clj tool which is now the recommended way to run Clojure anyway). 我会说在依赖管理方面得分很高,因为脚本完全是独立的(除了clj工具,现在无论如何都是推荐的运行Clojure的方法)。

File: ~/bin/script.sh 文件: ~/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)]]))

Then ensure it is executable: 然后确保它是可执行的:

$ chmod +x ~/bin/script.sh

And run it: 并运行它:

$ echo "stdin" | script.sh command line args
<div><span>Command line args: command, line, args</span><span>Stdin: stdin</span></div>

NB. NB。 This is primarily a shell script which treats the strings on line three as commands to execute. 这主要是一个shell脚本,它将第三行的字符串视为要执行的命令。 That subsequent execution will run the clj command line tool with the given arguments, which will evaluate those strings as strings (without side effects) and then proceed to evaluate the Clojure code below. 后续执行将运行带有给定参数的clj命令行工具,它将这些字符串作为字符串进行评估(没有副作用),然后继续评估下面的Clojure代码。

Note also that dependencies are specified as a map passed to clj on line three. 另请注意,依赖项被指定为第三行传递给clj的映射。 You can read more about how that works on the Clojure website . 您可以在Clojure网站上阅读有关其工作原理的更多信息。 The tokens in the dependency map are separated by commas, which Clojure treats as whitespace but which most shells do not. 依赖关系图中的标记用逗号分隔,Clojure将其视为空格,但大多数shell不会。

Thanks to the good folk on the #tools-deps channel of the "clojurians" Slack group whence this solution came. 感谢“clojurians”Slack集团的#tools-deps频道上的优秀人才,这个解决方案来了。

I know you asked for non project creating methods to accomplish this but as this specific issue has been on my mind for quite some time I figured I would throw in another alternative. 我知道你要求非项目创建方法来实现这个目标但是由于这个特定的问题已经在我脑海中浮现了很长一段时间,我想我会投入另一种选择。

TLDR: jump to the "Creating an Executable CLI Command" section below TLDR:跳转到下面的“创建可执行CLI命令”部分

Background 背景

I had pretty much the same list of requirements as you do a while back and landed on creating executable jar files. 我有一些相同的要求列表,就像你回过头来创建可执行的jar文件一样。 I'm not talking about executable via java -jar myfile.jar , but rather self-contained uber-jars which you can execute directly as you would with any other binary file. 我不是通过java -jar myfile.jar讨论可执行文件,而是直接执行自包含的uber-jars,就像使用任何其他二进制文件一样。

If you read the zip file specification (which jar files adher to as a jar file is a zip file), it turns out this is actually possible. 如果您阅读了zip文件规范 (jar文件作为jar文件是一个zip文件),事实证明这实际上是可行的。 The short version is that you need to: 简短版本是您需要:

  • build a fat jar with the stuff you need 用你需要的东西建造一个胖罐子
  • insert a bash / bat / shell script into the binary jar content at the beginning of your file 将bash / bat / shell脚本插入文件开头的二进制jar内容中
  • chmod +x the uber jar file (or if on windows, check the executable box) chmod +x超级jar文件(或者如果在windows上,检查可执行文件框)
  • rewrite the jar file meta data records so that the inserted script text does not invalidate the zip file internal offsets 重写jar文件元数据记录,以便插入的脚本文本不会使zip文件内部偏移无效

It should be noted that this is actually supported by the zip file specification. 应该注意,这实际上是由zip文件规范支持的。 This is how self extracting zip files etc work and the resulting fat jar (after the above process) is still a valid jar file and a valid zip archive. 这就是自解压zip文件等的工作原理以及生成的胖jar(在上面的过程之后)仍然是一个有效的jar文件和一个有效的zip存档。 All relevant commands such as java -jar still work and the file is now also executable directly from the command line. 所有相关命令(如java -jar仍然有效,该文件现在也可以直接从命令行执行。

In addition, following the above pattern it is also possible to add support for things like the drip jvm launcher which greatly accelerates the startup times of your cli scripts. 此外,遵循上述模式,还可以添加对滴水jvm启动器等功能的支持,从而大大加快cli脚本的启动时间。

As it turns out when I started looking into this about a year ago, a library for the last point of rewriting the jar file meta data did not exist. 事实证明,大约一年前我开始研究这个问题时,最后一个重写jar文件元数据的库并不存在。 Not just in clojure but on the JVM as a whole. 不仅仅是在clojure中,而是在整个JVM上。 This still blows my mind: the central deployment unit of all languages on the jvm is the jar file and there was no library out there that actually read the internals of jar files. 这仍然让我大吃一惊:jvm上所有语言的中央部署单元都是jar文件,那里没有实际读取jar文件内部的库。 Internals as in the actual zip file structure, not just what java's ZipFile and friends does. 实际的zip文件结构中的内部结构,而不仅仅是java的ZipFile和朋友所做的。

Furthermore, I could not find a library for clojure which dealt with the kind of binary structure the zip file specification required in a clean way. 此外,我找不到一个clojure库,它以简洁的方式处理zip文件规范所需的二进制结构。

Solution: 解:

  • octet has what I consider the cleanest interface of the available binary libraries for clojure, so I wrote a pull request for octet adding support for the features required by the zip file specification. octet有我认为可用的clojure二进制库最干净的接口,所以我写了一个八位字节pull请求,增加了对zip文件规范所需功能的支持。
  • I then created a new library clj-zip-meta which reads and interprets the zip file meta data and is capable of the offset rewriting described in the last point above. 然后我创建了一个新的库clj-zip-meta ,它读取并解释了zip文件元数据,并且能够进行上面最后一点中描述的偏移重写。
  • I then created a pull request to an existing clojure lib lein-binplus to add support for the zip meta rewriting implemented by clj-zip-meta and also add support for custom preamble scripts to be able to create real executable jars without the need for java -jar . 然后我创建了对现有clojure lib lein-binpluspull请求 ,以添加对clj-zip-meta实现的zip元重写的支持,并添加对自定义前导脚本的支持,以便能够创建真正的可执行jar而无需java -jar
  • After all this I created a leiningen template cli-cmd to support creating cli command projects which support all the above bells and whistles and has a well structured command line parsing setup...or what I considered well structured : ). 毕竟,我创建了一个leiningen模板cli-cmd来支持创建支持所有上述铃声和口哨的cli命令项目,并且具有结构良好的命令行解析设置......或者我认为结构良好的:)。 Comments welcomed. 欢迎评论。

Creating an Executable CLI Command 创建可执行CLI命令

So with all that, you can create a new command line clojure app with leiningen and run it using: 所以,您可以使用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!   

Where the output from the command is just the boilerplate sample command line parsing I've added to the leiningen template. 命令的输出只是样板命令行解析,我已添加到leiningen模板。

The custom preamble script is located at boot/jar-preamble.sh and it has support for drip. 自定义前导脚本位于boot/jar-preamble.sh ,它支持滴点。 In other words, if you have drip on your path, the generated executable will use it, otherwise it will fall back to standard java -jar way of launching the uber jar internally. 换句话说,如果你的路径上有滴水,生成的可执行文件将使用它,否则它将回退到内部启动uber jar的标准java -jar方式。

The source for the command line parsing and the code for the cli app live under the src directory as per normal. 按照正常情况,命令行解析的源代码和cli应用程序的代码位于src目录下。

If you feel like hacking, it is possible to change the preamble script and re-run lein bin and the new preamble will be inserted into your executable by the build process. 如果您想要黑客攻击,可以更改前导脚本并重新运行lein bin ,新的前导码将通过构建过程插入到您的可执行文件中。

Also it should be noted that this method still does java -jar under the covers so you do need java on your path. 还应该指出的是,这种方法仍然没有java -jar被窝里,这样你你的路径上需要的Java。

Ayway, long-winded explanation, but hopefully it will be of some use for somebody with this problem. Ayway,啰嗦的解释,但希望这对于有这个问题的人有用。

Consider Lumo , a ClojureScript environment which was specially designed for scripting. 考虑Lumo ,一个专为脚本编写而设计的ClojureScript环境。

Note that while it supports both ClojureScript (JAR) and NPM dependencies, the dependency support is still under development . 请注意,虽然它支持ClojureScript(JAR)和NPM依赖项,但依赖性支持仍在开发中

我编写了许多Clojure(JVM)脚本,并使用CLI-matic库https://github.com/l3nz/cli-matic/来抽象出大多数与命令行解析,创建和维护相关的样板文件。帮助,错误等

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM