Groovy 仅凭这点永远踏进不了 Linux 的脚本编程

Linux 脚本编程

作为一个具备 Linux 编程能力的脚本语言

  1. 方便的 navive 调用支持
  2. 可以源代码的形式运行
  3. 具有高效率的编程和便捷的文本处理能力
  4. 众多高级的动态特性

貌似 Groovy 都具备了,Groovy 将 Python 和 Ruby 的语法和概念优点结合起来,语言本质上来讲也并不比它们差。

但是

纵观 Python、Ruby、Perl 等语言,它们的运行时非常轻量级,所以启动速度极快。这对于一个脚本程序而言还是有一定程度的重要性,因为很多脚本完成任务可能瞬间就结束了,它们的功能通常是 IO 后的副作用(不产生结果,改变自身环境外的其他元素)。
而 groovy 程序由于要启动一个 JVM,启动时间相对而言“相当长”。人家的脚本可能都跑完了你才启动好。
当然,这算不上什么致命缺点。反观不重视启动时间的长时间运行脚本,groovy 还有 JVM 高效率的执行速度(JIT的功劳)的优势。

当然我指的不是启动速度这个缺陷,不然也不会说这方面的双面性了。我想说的是:groovy 的包管理策略。

包管理劣势

Groovy 自身并没有带包管理工具,所有的依赖都要靠第三方工具例如 Gradle 来管理。如果依赖了 Gradle,那么包括运行都需要用 Gradle。
因为 Groovy 和 Java 的库依赖策略是一致的。

如果一个存在第三方依赖的 java 程序想单独运行,需要这样做:

java -Djava.ext.dirs=./lib Task

上面加载了当前 lib 目录中的库,然后运行了 Task 程序。也就是说,需要把依赖的 jar 手动放到 lib 目录。
而 python 或者 ruby 那样包管理工具会直接把包安装到全局路径中。无需任何指定库目录的参数即可单独运行。

如果你想像 python 或者 ruby 那样,维护一个全局的 lib 目录,然后在启动上固定一个 目录参数达到这样的效果,也不好。 因为 JVM 启动时会加载全部 jar 文件,而 python 或 ruby 只会在运行时导入指定的库(根据 import 或者 require 函数)。
假若为 java 维护一个全局 lib 目录。那么这个 lib 中的库会越来越多。启动时间越来越长,造成不必要的资源浪费。
假如 A 程序依赖 库1,C 程序依赖 库2。在拉取玩两个项目依赖以后,每次运行 A C 的任何一个都会载入 库1和2。
所以这样是行不通的。

要么,我们只能这样做:

java -cp  ./lib/spring.jar:./lib/hibernate.jar Task

来载入仅需要的依赖库。但是未免执行语句太长和麻烦了点。对于依赖的变更也非常不灵活。

同理,groovy 也可以采用类似的做法,可以比 Java 优雅一点。把它写在 Linux 脚本解析的 Shebang 里边:

#! /usr/bin/groovy -cp  ./lib/spring.jar:./lib/hibernate.jar
// groovy code...

执行的语句就不用那些参数了。但是仍然是不灵活。说实话,太死板了。

什么样的才更适合

groovy 需要这样一个包管理工具:它提供一个导入函数,可以动态的在运行时导入指定依赖(jar文件)。
并且它有一般的包管理的网络拉取依赖功能,拉取后的库存在的路径一定是统一的或者能被“导入函数”检索到的。并且这种程序是完全有可能的作为一个 groovy 程序开发出来的,基于 classLoader 即可。

这样,一个 Groovy Script 才能作为 Linux 脚本单独、广泛的运行。不然一个存在依赖的小脚本迁移还得像发布一个 Java 项目那样麻烦,那还叫什么脚本呢?

惊喜

首先感谢你看到这里。下面我说明一下:

这篇文章是我大概一年半以前刚听说 groovy 这门语言的时候,企图用它代替 python 写 Linux 脚本。于是现学现用了,然后发现这个缺陷实在是难以忍受,它让每一次脚本变更都跟发布 Java 项目一样麻烦。一怒之下,果断放弃了 Groovy 而写的文章。因为新博客诞生了,我又把它迁移过来了。 老实讲,那时候真有点目光短浅。没有经过大量的资料查找或是我当时确实没有找到。
其实我写的这些,Groovy 内置就已经支持了。那就是 Grape
它相比 ruby 和 python 不同的是,它可以动态的网络下载依赖。也就是在运行时下载,不用运行前像 gem 或者 pip 安装一遍。

例如你可以创建一个 test.groovy

import org.apache.commons.collections.primitives.ArrayIntList

@Grab(group='commons-primitives', module='commons-primitives', version='1.0')
def createEmptyInts() { new ArrayIntList() }

def ints = createEmptyInts()
ints.add(0, 42)
assert ints.size() == 1
assert ints.get(0) == 42

直接 groovy test.groovy 运行。它能正常导入 @Grab 注解配置的库,并且缓存下来。下次执行无需网络拉取。

总结

  1. 在没有调查明白之前,不要轻易放弃一个东西。Groovy 也很棒,值得大家使用。
  2. 本文标题党了。