任何时候,和很多技术人员一样,我会需要运行一些 CLI(命令行界面)命令去执行一个快速,通常重复的任务。操作系统提供许多命令(如 ls,cat,cp,rm,grep ...),它们可以合并在一起以执行一些任务(像在亚马逊部署应用,或者是发现某些地方的最便宜的航班)。人们给他起了一个庄严的名字:Unix 哲学,这个方法能够用在任何操作系统中。
Unix 方式能提高你的操作效率,为此,很多人甚至认为 Unix 就是一个 IDE。
但是,很多时候,你会发现自己试图做一些不能轻易完成的事情,只需使用 CLI 中现有的工具。换句话说,你必须直接在 CLI 中写些内置代码,或者写你自己的命令。那么问题来了:在所有主要操作系统中,进行两者中的任一操作对 JVM 程序员来说都不是很方便。
对于更复杂的逻辑,你当然可以写 bash 脚本来实现,也可以直接用 C 或者 Rust 写本地可执行文件。但对于像我这样不喜欢(或没时间)学习新的编程环境的人,这并不是一个好建议。这类人会沉浸在 JVM 的世界,希望他们的程序可以不做任何改变就能用于任何操作系统。他们已经熟悉了丰富的 JVM 库,不需要再去尝试像 bash 或 awk 这些古老的语言。
这就是我创建 OSGiaaS-CLI 项目的原因。
OSGiaaS-CLI 项目把若干小型库集中在一起,基于 OSGi services 创建高度模块化 CLI ,可以像在 REPL 中那样运行代码片段。而 OSGi services 由 Java 或其它 JVM 语言实现。
在详细说明之前,先看一个使用 OSGiaaS-CLI 的示例(用 Java 8 Lambda 简单地处理 ls 命令输出的每一行):
>> run ls | java line -> line.contains("log") ? line : null
如果一个命令起始于 >> 提示符,表示这个命令是在 OSGiaaS-CLI 中输入的。如果没有 >> 提示符,则命令是 W 在 h 普通的 shell 环境中输入。
因为 Java 有点冗长,所以我通常使用更简洁的 Groovy 语法:
>> run ls | groovy if (it.contains("log")) it
我们都知道 ls 命令是用来干啥的(列出文件),然后会对 ls 输出的每一行运行 Groovy 脚本,在 it 包含 “log” 的时候返回它(返回的 对象 由 Java 或 Groovy 打印出来,就像其它语言的命名那样)。
完成上述任务最简单的方法是使用 grep: 运行 ls | grep log。
上面的例子只是简单地说明在 CLI 中使用 JVM 语言是件容易的事情。
我们可以使用任何由 OSGiaaS 工程支持的其他语言(当前支持:Java, Groovy, JavaScript ,处在实验中的是: Frege , Clojure 和 Scala ),并可以直接在 CLI 中运行。
为了证明这一点,这里有一个使用一堆 JVM 语言的简单例子,并使用了同样的命令行管道(这个例子使用 CLI 多行支持,通过 :{ 开始,:}结束 ):
>> :{ frege sum [1,2,3] | groovy it.toInteger() + 2 | java line -> Integer.parseInt(line) + 3 | scala (line: String) => line.toInt + 4 | clj (fn [line] (+ (read-string line) 5)) | js function (line) { return line == 20; } :} true
编译指令可以写入任何 JVM 语言,还包括 Kotlin ,及 特别努力的 Ceylon 。你甚至不需要停止 CLI 重新加编译指令,就能看到更改的代码被重编译并重新加载。
只要你之前有足够长的 CLI 经验,你就会感到这个基于 JLine 的 CLI 不错,它支持命令历史,行编辑,通用 unix 快捷键(包括 vim/emacs 模块),管道(像上面演示的那样),tab 自动完成等等。
如果你用 Gradle ,最容易的方式是运行下面脚本的 createOsgiRuntime 任务(运行 gradle 的crOsgi),然后使用生成的运行脚本。
详细步骤如下:
1. 保存下面的文件并命名为 build.gradle:
plugins { id "com.athaydes.osgi-run" version "1.5.4" } repositories { mavenLocal() jcenter() } dependencies { // the osgiaas-cli core bundle osgiRuntime 'com.athaydes.osgiaas:osgiaas-cli-core:0.7' // OSGi Service Component Runtime implementation osgiRuntime 'org.apache.felix:org.apache.felix.scr:2.0.2', { exclude group: '*' } } runOsgi { bundles = [ ] // all bundles added as osgiRuntime dependencies }
同样地, 运行下面的命令,下载并保存文件:
curl https://raw.githubusercontent.com/renatoathaydes/osgiaas/master/samples/osgiaas-cli-minimal.gradle --output build.gradle
2. 在同样的文件夹下,运行 createOsgiRuntime Gradle 任务(可以简单使用 crOsgi):
gradle crOsgi
OSGiaaS 使用 osgi-run Gradle 插件来创建运行任务. 通过插件取得项目依赖, 从 JCenter 下载 并 全部放入一个 OSGi 运行任务。 OSGi 配置很简单 . 查看 osgi-run 文档获得更多信息.
3,使用下面命令开始 OSGiaas-CLI:
在 Linux/Mac 中:
bash build/osgi/run.sh
在 Windows 中:
build/osgi/run
你可以看看 OSGiaas CLI 的 ASCII 艺术 logo:
如上所示。
你现在就可以准备使用 CLI。
点击 tab 几乎可以在任何地方实现自动补全,例如,想得到些帮助(在句末按空格键),然后 tab。
第一个你需要试的命令是 ps(Felix Shell 提供):
这个命令展示当前系统中所有安装的库。
OSGi 默认的运行时 Apache Felix(就是上面提到的系统)。
如果你想使用不用的运行服务像 Equinox,改变 build.gradle 文件,添加 configSettings = 'equinox' 在 runOsgi 代码块内部用做 osgi-run 文档的解释。
Gradle 建立的文件是我们目前 OSGiaaS-CLI 运行,声明唯一最小的依赖设置。你可以点击 Tab 按键去看哪一个命令是有效的:
OSGiaaS-CLI 文档给了详细解释对于他提供的命令用法,可是你可以使用帮助命令去看任意命令的使用信息:
ci(内部详情命令)命令甚至给了一个命令的命令信息,包括那个 bundle 提供的命令和实现的类名(例如,ci -v alias).
其他的一些你从开始就应该知道的命令是:
shutdown - 退出 CLI
lr - 列出 JMV 资源
grep - 过滤文本
highlight - 高亮文本
run - 运行本机命令
使用命令时,你不需要一直输入,使用 CLI 当做 REPL 非常好用,像我们看到的那样。
因为 OSGiaaS-CLI 可以运行本机命令,你可以让它和 OSGiaaS-CLI 混合使用,就像你在自己的机子上使用 JVM 编写命令一样。
例如,运行 netstat(本地命令)和高亮所有包含 72 的代码行:(使用用 Java 编写的 CLI 的高亮命令)
在需要临时运行本地 shell 命令的时候,可以使用 use 命令(一旦通过 use 运行某个命令,所有用户输入都)会被当作它的参数:
在使用某个命令的过程中,你可以在调用前缀 `_` 的命令。这会停止使用该命令。调用 _use,不带任何 W 参数,表示 “不 use 任何命令”。
从 CLI 转换到语言的 REPL,只需要 use 语言这样的命令:
不过, 在使用语言命令之前,你得确保安装了这个语言! 下一节中就会看到怎样在环境中安装更多命令。
在继续这前,先运行关闭命令退出 CLI。
在 OSGi 运行时中,你可能需要输入 stop 0 来停止系统捆绑。
我比较喜欢定义一个别名:
>> alias exit="stop 0"
现在可以直接输入 exit 来退出。
为了保存这个别名,需要把上面的命令添加到初始化文件中,这个文件在 "${user.home}/.osgiaas_cli_init"。每次启动 CLI 都会运行这个文件,所以你可以用它定制 CLI。
我的整个初始化文件如下:
~/.osgiaas_cli_init:
color prompt blue prompt "osgiaas> " color error yellow alias exit="stop 0" alias hl=highlight
想要管理安装到 OSGiaaS-CLI 环境中的库文件,使用上面开始用到的 Gradle 文件是最简单的。
举个例子,为了增加 JavaSlang 这样的 Java 库文件到 OSGiaaS-CLI 环境,只需要通过 Gradle 文件增加这个库文件的依赖 (增加的行加粗显示):
plugins { id "com.athaydes.osgi-run" version "1.5.2" } repositories { mavenLocal() jcenter() } dependencies { // the osgiaas-cli core bundle osgiRuntime 'com.athaydes.osgiaas:osgiaas-cli-core:0.7' osgiRuntime 'io.javaslang:javaslang:2.1.0-alpha' // OSGi Service Component Runtime implementation osgiRuntime 'org.apache.felix:org.apache.felix.scr:2.0.2', { exclude group: '*' } } runOsgi { bundles = [ ] // all bundles added as osgiRuntime dependencies
保存 Gradle 文件后, 确保重新创建环境:
gradle crOsgi
如果你想确保只安装 Gradle 文件中的库文件,还可以运行 clean 任务: gradle clean crOsgi。
现在,重启 CLI,你会发现环境中已包含 JavaSlang 库文件。
可以用同样的方法添加其它有用的命令:
osgiRuntime 'com.athaydes.osgiaas:osgiaas-cli-javac:0.7'
osgiRuntime 'com.athaydes.osgiaas:osgiaas-cli-groovy:0.7'
osgiRuntime 'com.athaydes.osgiaas:osgiaas-cli-js:0.7'
你已经学会了!
环境由 CLI 自身来管理。
很多命令只在 OSGi 环境中存在,比如 install(从某个 URL 安装包到系统中)、start(启动一个包),stop(停止启动的包)。
这些命令会让你感受到 OSGi 环境的魅力。如果你有 jar 包想安装在系统中,你可以使用 install 命令:
>> install file:///Users/renato/jars/my-lib.jar
install 命令使用 URL,所以你也可以从远程服务器获取 jar 文件。
install 命令不会自动启动安装好的包。start 命令也可以使用 URL 作为参数,它会先安装这个包再启动它。 多数情况下只需要使用 start 命令。
不过以这种方式来下载 jar 文件相当不方便,尤其是存在依赖链的时候。使用像 Gradle 或 Ivy 之类的包管理器要容易得多。
如果你想使用 Gradle 来获取 jar 文件,你可以使用上一节中提到的方法(在构建文件中添加 osgiRuntime 依赖,再次构建的时候它会被安装到系统中)。
OSGiaaS-CLI 还提供了一个办法,直接在 CLI 中使用 Apache Ivy !
你通过在 Gradle 文件中添加下面的依赖项,在 CLI 中安装 ivy 命令:
osgiRuntime 'com.athaydes.osgiaas:osgiaas-cli-ivy:0.7'
只要你愿意,可以用当前包含 Ivy 命名的的构建文件代码“default” CLI 构建,除了调试配置:
curl https://raw.githubusercontent.com/renatoathaydes/osgiaas/master/samples/osgiaas-cli-default.gradle --output build.gradle
之后,用 gradle clean crOsgi 重新构建项目,再启动 CLI。
现在你可以使用 ivy 命令检索存在于本地 Maven 库或 JCenter(包含 Maven核心工具) 的工具。比如,要检索 JavaSlang 并立即启动,输入如下命令:
>> ivy io.javaslang:javaslang | start
你可以使用一个 CLI 语言模块 轻松地使用 JavaSlang(或其它你想使用的库),比如用 Grovvy:
>> use groovy Using 'groovy'. Type _use to stop using it. >> import javaslang.collection.List >> List.of(1,2,3).intersperse("-").mkString() 1-2-3
通过 osgiaas-cli-groovy 文档的指南来了解如何运行 Groovy 模块。不幸的是,大多数语言模块都需要一些小小的配置,因为它们中大多数使用的不是由 OSGi 环境导出的非标准类...比如 Grovvy 模块需要 run.reflect。你也可以从一个 示例 的 Gradle 文件开始。
创建一个新的,基础的 CLI 命令,你需要去创建一个类实现 org.apache.felix.shell.Command 接口。
这有一个 Java Hello World 命令,例如:
package com.athaydes.osgiaas.examples.java; import com.athaydes.osgiaas.cli.CommandHelper; import org.apache.felix.shell.Command; import org.osgi.service.component.annotations.Component; import java.io.PrintStream; import java.util.List; @Component( immediate = true, name = "hello-java" ) public class HelloJavaCommand implements Command { @Override public String getName() { return "hello-java"; } @Override public String getUsage() { return "hello-java [<message>]"; } @Override public String getShortDescription() { return "Prints a Hello World message or a custom message given by the user"; } /** * This method implements the command logic. * * @param line full command provided by the user. Notice that this may be more than one line! * @param out stream for the command output (prefer this to System.out) * @param err stream for the command errors (prefer this to System.err) */ @Override public void execute( String line, PrintStream out, PrintStream err ) { // break up the command line into separate tokens. // notice that the first part is always the name of the command itself. List<String> arguments = CommandHelper.breakupArguments( line ); switch ( arguments.size() ) { case 1:// no arguments provided by the user out.println( "Hello Java!" ); break; case 2:// The user gave an argument, print the argument instead out.println( "Hello " + arguments.get( 1 ) ); break; default: // too many arguments provided by the user CommandHelper.printError( err, getUsage(), "Too many arguments" ); } } }
完整的命令同样能给用户提供些操作,如自动补齐和优化文档。OSCiaas 项目提供些设备能够简化这些操作。
ArgSpec 类可以用来说明可能使用的命令,包括能在标准方法中自动生成命令文档的文档。
为了展示他的工作原理,我们将创建一个 Weather 的命令,让用户看到当前全球的天气,就像下周的天气预报一样。