Groovy食谱: 第5章 命令行中的Groovy

第5章 命令行中的Groovy

用于shell脚本的Java ?是的,对的。

另一方面,Groovy在这方面给了我惊喜。现在,不要误解我的意思——没有哪个自重的Unix系统管理员会抛弃自我混淆的Perl和shell脚本,转而支持Groovy。但是对我来说,使用我非常熟悉的语言来完成服务器上的日常工作是一个完美的选择。我不是一个全职的系统管理员,但是我一直面临着一些杂事,比如费力地通过一个充满Tomcat日志文件的目录,或者批量地将一个充满图像的目录从一种格式转换成另一种格式。用Groovy做这种事情是如此自然,以至于我无法想象用其他任何语言来做这件事。

在本章中,我们将讨论如何从命令提示符运行未编译的Groovy脚本,并从用户处获取命令行参数。您可以像调用本机操作系统命令一样轻松地调用其他Groovy脚本。Groovy作为胶水语言的才能在这里得到了充分的展示。Groovy泰然自若地模糊了本机操作系统任务和Java库之间的区别,执行管理任务——我敢说吗?非常愉快。

5.1 运行未编译的Groovy脚本

groovy hello.groovy
groovy hello

groovy命令允许您运行未编译的Groovy脚本。 例如,在您选择的文本编辑器中创建一个名为hello.groovy的文件。 向其添加以下行:

println "Hello Groovy World"

要运行脚本,请键入groovy hello.groovy。如果您使用".groovy"文件扩展名,您可以在命令提示符中键入扩展名时关闭该扩展名: groovy hello

对于我们这些精通企业Java开发和“编译 -> JAR -> WAR -> EAR -> 部署”生命周期的人来说,认为我们实际上只需要保存一个文件并运行它,似乎有些不切实际。一旦你体验过“想一下 -> 编码 -> 运行”,你就会对它上瘾。

5.2 打捆Groovy

#!/usr/bin/env groovy
println "Hello Groovy World"

类unix操作系统的爱好者熟悉他们的脚本的“shebanging”—“hash”和“bang”的缩写,即脚本第一行的前两个字符。敲打脚本可让您在命令行输入时省去命令解释器。无需键入groovy hello来运行此脚本,您只需键入hello.groovy。 因为脚本是自我感知的(也就是说,它已经知道它是一个Groovy脚本),所以在命名文件时甚至可以省略文件扩展名。在命令提示符中键入hello使它看起来像一个本机命令。

Shebanging Groovy脚本假设有四件事:

  • 您使用的是类似Unix的操作系统:Linux,Mac OS X,Solaris等(很抱歉,Windows用户,除非您也是Cygwin [^502]用户)。
  • 您已将文件设置为可执行文件(chmod a+x hello)。
  • 当前目录(.)在您的PATH中。 如果不是,./ hello还算不错。
  • 环境变量GROOVY_HOME存在,GROOVY_HOME/bin在您的路径中的某个地方。您总是可以在脚本的顶部硬编码到groovy命令解释器的确切路径,但这将阻止您使用第2.1节(在Unix、Linux和Mac OS X上安装groovy,见第25页)中讨论的符号链接技巧在不同版本的groovy之间切换。

我有许多实用程序脚本保存在~/bin中。她们已经在我的PATH上了。这意味着,无论我在文件系统的哪个位置,我都可以键入一些聪明的操作,在某种程度上模糊地记得我是用Groovy编写脚本的,但老实说,我并不关心。

5.3 接受命令行参数

if(args){
  println "Hello ${args[0]}"
} else{
  println "Hello Stranger"
}

还记得编写您的第一个HelloWorld Java类吗?大概是这样的:

public class HelloWorld{
  public static void main(String[] args){
    if(args != null && args.length > 0){
      System.out.println("Hello " + args[0]);
    } else{
      System.out.println("Hello Stranger");
    }
  }
}

在编译完javac HelloWorld.java之后,您可以通过输入java HelloWorld Bub来运行它。

使用Groovy,您可以将相同的练习简化为基本要领。 在一个名为Hola.groovy的文件中键入启动此技巧的代码。 接下来键入Hola Bub。 由于groovy命令解释器将所有Groovy脚本都编译到内存中的有效Java字节代码中,因此您可以有效地得到Java示例,而不必键入所有其他示例代码。

这个简洁的if语句之所以有效,是因为Groovy的真理。有关更多信息,请参见第54页第3.10节,Groovy真相。

每个Groovy脚本都有一个隐式的args字符串数组,它表示传入脚本的命令行参数。(你猜对了——这就是《public static void main》的args。)要查看magic args数组的运行情况,请创建一个名为cli.groovy的文件。,然后键入以下内容:

args.each{println it}

输入groovy cli this is a test,结果如下:

$ groovy cli this is a test

===>
this
is
a
test

5.4 运行Shell命令

// in Windows:
println "cmd /c dir".execute().text

//in Unix / Linux / Mac OS X:
println "ls -al".execute().text

运行shell命令就像在字符串上调用execute()一样简单。这将返回一个java.lang.Process。您可以使用此技巧来运行完整的程序或简单的命令行任务。正如代码示例所演示的,字符串中的命令很可能在不同的操作系统之间有所不同。ls命令只适用于Mac OS X、Unix和Linux系统。dir命令只适用于Windows衍生工具。

如果仅在字符串上调用" .execute()“,则不会捕获结果输出文本。 对于" rm some-file.txt".execute()之类的命令,这可能是可以接受的。 如果要查看shell命令返回的输出,请在” .execute()“的末尾附加” .text"。

在类似Unix的系统上,大多数shell命令实际上是可执行程序。 键入哪个" ls"以查看命令的路径。 这意味着您通常在命令行中键入的几乎所有内容都可以用引号引起来并执行。 (此规则的一个不幸例外是在处理通配符时。有关更多详细信息,请参见下一页的第5.5节,在Groovy脚本中使用Shell通配符。)例如,您可以运行println "ifconfig".execute().text查看当前网络设置。

在Windows系统上,println "ipconfig /all".execute().text返回相似的结果。 因为"ipconfig.exe"位于" c:\windows\system32"中的路径上,所以可以使用此技巧。 不幸的是,您在Windows中的命令提示符下键入的许多最常见命令根本不是可执行程序。 进行搜索时,您永远找不到隐藏在系统目录中的dir.execopy.com。 这些命令嵌入在 " cmd.exe" 中。

要执行它们,必须键入 " cmd /c"。 有关嵌入式命令的列表,请键入" cmd/?"。 在命令提示符下。 您将在Windows XP上看到以下列表:

DIR
COPY
REN
DEL or ERASE
COLOR
CD or CHDIR
MD or MKDIR
PROMPT
PUSHD
POPD
SET
SETLOCAL
ENDLOCAL
IF
FOR
CALL
SHIFT
GOTO
START
ASSOC
FTYPE

知道了这一点,许多Windows用户只是将"cmd /c"放在他们在Groovy中执行的所有命令之前。 尽管有点冗长,但键入"cmd/c ipconfig /all".execute().text肯定不会对您造成任何伤害。

Windows用户的最后一点建议-不要忘记在目录中转义反斜杠:println "cmd /c dir c:\\tmp".execute().text

5.5 在Groovy脚本中使用Shell通配符

//in Windows:
println "cmd /c dir *.groovy".execute().text
def c = ["cmd", "/c", "dir *.groovy"].execute().text
println c

//in Unix / Linux / Mac OS X:
def output = ["sh", "-c", "ls -al *.groovy"].execute().text
println output

//sadly, these don't work
println "ls -al *.groovy".execute().text
println "sh -c ls -al *.groovy".execute().text

在上一页的5.4节,运行Shell命令中,您了解到,在Windows机器上键入的一些常见命令(目录,副本等)已嵌入在"cmd Shell"中。 该外壳还可以管理通配符扩展。 因此,要求所有以".groovy"结尾的文件都是shell扩展为列表然后传递给dir命令的事情。

在类似Unix的系统上,外壳程序还负责扩展通配符。 知道这一点后,在命令中明确包含外壳程序是有意义的。 您可以输入sh -c " ls -al * .groovy" 了解我们要完成的工作。

不幸的是,如果我尝试在单个字符串上调用execute,则此命令所需的嵌入式引号会引起我极大的伤痛。 幸运的是,我们也可以在String数组上调用execute。 数组中的第一个元素是命令,所有随后的元素都作为参数传递。 尽管这种形式较为冗长(乍一看也不太直观),但它确实有效。 我们为样式点获得-1,但为完成工作而获得+1。…

5.6 一次运行多个Shell命令

//in Windows:
println "cmd /c dir c:\\opt & dir c:\\tmp".execute().text
//in Unix / Linux / Mac OS X:
println "ls /opt & ls /tmp".execute().text

您可以使用“&”字符将多个shell命令串在一起。 当然,这与Groovy无关-这是基础OS的功能。 为了证明这一点,请尝试直接在命令提示符下键入用引号引起来的命令。

5.7 等待Shell命令完成后再继续

def p = "convert -crop 256x256 full.jpg tile.jpg".execute()
p.waitFor()
println "ls".execute().text

如果您有一个运行时间较长的命令,并希望在继续操作之前等待它完成,则可以将该命令分配给一个变量,然后使用" .waitFor()“方法。 此示例显示了ImageMagick命令"convert -crop”,该命令拍摄大图像并将其分解为"256 x 256"像素的图块。 在显示当前目录的目录列表之前,您需要等待命令完成,以确保显示所有生成的图块。

5.8 获取系统属性

println System.getProperty("java.version")
===> 1.5.0_07

System.properties.each{println it}
===>
java.version=1.5.0_07
java.vm.vendor="Apple Computer, Inc."
os.arch=i386
os.name=Mac OS X
os.version=10.4.10
user.home=/Users/sdavis
...

JVM为您提供了一个舒适的沙箱,保护您的代码不受操作系统差异的影响。Sun创造了“一次编写,随处运行”(WORA)这个短语来描述这种现象,尽管老前辈和愤世嫉俗者将其稍作修改为“一次编写,到处调试”。

您在本章中所做的几乎所有事情都将WORA视作眼中钉。 您正在操作系统级别上乱七八糟,如果您尝试在明确为其编写操作系统的任何地方运行它们,几乎可以肯定会破坏这些命令。 鉴于此,很高兴能够以编程方式确定您所运行的硬件类型,所使用的JVM版本,等等。 "System.properties"哈希表使您可以进行这种自省。

如果您已经知道要查找的变量的名称,则可以明确要求它; 例如, System.getProperty(" file.separator")可让您知道您是处于正斜线状态还是反斜线状态。

另一方面,你可能会想去逛街。输入System.properties.each{println it}允许您一个一个地输出完整的属性列表。这是一个展示运行系统中所有有趣部分的好工具。我通常在每个生产服务器上运行这个Groovlet,这样我就可以远程监视它们。(有关groovlet的更多信息,请参见第2.6节,在Web服务器上运行Groovy (groovlet),见第33页。有关防止私有位成为公共位的更多信息,请参阅关于安全域的Tomcat文档[^508])。

以下是出现在我的MacBook Pro上的各种有用的系统属性:

java.version=1.5.0_07
java.vendor=Apple Computer, Inc.
java.vendor.url=http://apple.com/
java.home=/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home
groovy.home=/opt/groovy
java.class.path=/path/to/some.jar:/path/to/another.jar

file.separator=/
path.separator=:
line.separator=[NOTE: this is the OS-specific newline string.]
os.name=Mac OS X
os.version=10.4.10
os.arch=i386

user.dir=/current/dir/where/you/ran/this/script
java.io.tmpdir=/tmp
user.home=/Users/sdavis
user.name=sdavis
user.country=US
user.language=en

file.separator, path.separator, and line.separator

您已经知道,这些是在Windows和类unix操作系统之间最常见的差异。

user.dir

这是当前目录(运行类的目录)。 如果您要查找相对于当前位置的目录和文件,则知道user.dir是很好的。

java.io.tmp

这是写入短期临时文件的好地方。 尽管确切的文件路径有所不同,但每个系统上都存在此变量。 有一个通用的垃圾场,可以保证在每个系统上都存在,是一个很好的隐藏的宝石。 只是不要期望这些文件会超出当前的执行范围。

user.home

尽管确切的文件路径各不相同,但可以保证每个小系统(如“ java.io.tmp”)都存在于每个系统上。 这是写入更多永久数据的好地方。

Reading in Custom Values from -D or JAVA_OPTS
System.properties哈希表不仅可以处理每个系统上出现的无聊的旧默认值,还可以提供更多好处。 自定义值可以通过两种方法传递到System.properties中。 如果您曾经在Ant目标中使用过“-D”参数(例如,ant -Dserver.port = 9090 deploy),则您知道它们也会显示在 System.properties中(System.getProperty(“server.port”))。 存储在JAVA_OPTS环境变量中的值也显示在System.properties中。

5.9 获取环境变量

println System.getenv("JAVA_HOME")
===> /Library/Java/Home

System.env.each{println it}
===>
PWD=/Users/sdavis/groovybook/Book/code/cli
USER=sdavis
LOGNAME=sdavis
HOME=/Users/sdavis
GROOVY_HOME=/opt/groovy
GRAILS_HOME=/opt/grails
JAVA_HOME=/Library/Java/Home
JRE_HOME=/Library/Java/Home
JAVA_OPTS= -Dscript.name=/opt/groovy/bin/groovy
SHELL=/bin/bash
PATH=/opt/local/bin:/usr/local/bin:...

与系统属性类似(如第5.8节,获取系统属性,在第92页中所述),环境变量是挖掘系统特定信息的另一个丰富途径。

如果您已经知道要查找的环境变量的名称,则可以明确要求它; 例如,System.getenv("GROOVY_HOME")可让您知道Groovy的安装目录。 要遍历系统上的所有环境变量,请执行System.env.each {println it}

您可能会注意到环境变量和系统变量之间有一些重叠。 例如,System.getProperty("groovy.home")System. getenv("GROOVY_HOME")都得到同样的事情:/opt/groovy。 有时,您要查找的特定信息只能在一个地方或另一个地方找到。 例如,环境变量列表可能包含未出现在系统属性列表中的变量,例如TOMCAT_HOME,JBOSS_HOME和ANT_HOME。

像其他任何东西一样,在不同的时间提供给您都很重要。 您的自定义调整可能通过环境变量或-D参数进入。 这些变量可能会将您指向用户的主目录或可以找到配置文件的应用程序目录,例如server.xmlstruts-config.xml.bash_profile。 重要的是,无论使用哪种特定机制,您都将能够管理整个系统。

5.10 计算一个字符串

def s = "Scott"
def cmdName = "size"
evaluate("println s.${cmdName}()")
===> 5
cmdName = "toUpperCase"
evaluate "println s.${cmdName}()"
===> SCOTT

在第89页的5.4节中,我们讨论了如何对任意字符串调用execute。evaluate的工作方式略有不同。

evaluate不运行shell命令,而是允许您以Groovy代码的形式动态执行随机字符串。前面的示例动态地调用了String的size()toUpperCase()上的两个方法。(你注意到第二个例子中的可选括号了吗?)这带来了一些有趣的功能,比如能够迭代一个对象上的所有方法并调用它们:

//NOTE: This is pseudocode -- it won't actually run
def s = "Scott"
s.class.methods.each{cmdName ->
  evaluate("s.${cmdName}()")
}

尽管此示例无法按书面形式运行-它没有考虑到一些字符串方法需要的参数,比如“s.substring(2,4)”它显示了动态评估Groovy代码的潜在价值。它也很好地说明了其中的风险。如果您盲目地接受终端用户的命令并动态地执行它们,那么您应该为发送给您rm -Rf /的脚本准备好。有关动态评估方法的工作示例,请参见第10.4节,类方法的发现,见第188页。

5.11 调用另一个Groovy脚本

// hello.groovy
println "Howdy"

// goodbye.groovy
hello.main()
println "Goodbye"

您可能一直从另一个Java类中调用一个Java类。如果这两个类在同一个包中,您可以直接从另一个调用: “AnotherClass.doSomething();”。如果它们位于单独的包中,则需要导入另一个包或完全限定类: “com.else.anotherclass.dosomething();”。从另一个Groovy脚本调用另一个Groovy脚本的工作原理基本相同。只要您记住Groovy代码被动态地编译为字节码,就永远不会出错。

在前面的例子中,"hello.groovy"被编译成如下等价的Java代码:

public class hello{
  public static void main(String[] args){
    System.out.println("Howdy");
  }
}

小写的类名可能看起来很奇怪,但Groovy只是使用文件名作为类名。(听起来很熟悉吗?)没有显式封装在函数/闭包/脚本的"public static void main(String[ ] args)"中的脚本内容。位于相同目录中的两个脚本实际上位于相同的包中。因此,调用与所在目录相同的脚本与调用类上的静态main方法一样简单。

使用参数调用另一个脚本

//hello2.groovy
if(args){
  println "Hello ${args[0]}"
  if(args.size() > 1){
    println "...and your little dog, too: ${args[1]}"
  }
}

//goodbye2.groovy
hello2.main()
hello2.main("Glenda")
hello2.main("Dorothy", "Toto")
println "Goodbye"

由于脚本体实际上是"public static void main(String[ ] args)"方法,因此只能通过提供的字符串数组传递参数。

在另一个脚本中调用方法

//hello3.groovy
if(args){
  println "Hello ${args[0]}"
  if(args.size() > 1){
    println "...and your little dog, too: ${args[1]}"
  }
}

def sayHola(){
  println "Hola"
}

//goodbye3.groovy
hello3.main()
hello3.main("Glenda")
hello3.main("Dorothy", "Toto")
println "Goodbye"
h = new hello3()
h.sayHola()

如果其他脚本有静态方法(如main),则可以静态地调用它们。如果其他脚本定义了实例方法,则必须在调用它们之前实例化脚本。

调用不同目录中的另一个脚本

evaluate(new File("/some/other/dir/hello.groovy"))

我们的朋友evaluate又来了。(参见第95页第5.10节,求值字符串,用于求值的另一种用法。)这一次,您要计算的是一个文件,而不是一个任意字符串。这实际上调用了另一个文件的主方法。

如果您试图使用脚本到脚本的调用来做比我们已经讨论过的更复杂的事情,我的建议是将您的脚本编译成字节码,将它们放在您选择的包中,将它们打包,然后像调用其他任何Java类一样调用它们。

5.12 动态的Groovy (Groovy -e)

$ groovy -e "println System.properties['java.class.path']"
===>
/opt/groovy/lib/groovy-1.1-beta-2.jar:/System/Library/Frameworks
/JavaVM.framework/Versions/1.5.0/Classes/.compatibility/14compatibility.jar

Groovy使快速运行代码变得很容易。您可以保存一个文件并立即运行它。您可以打开一个快速的Groovy shell或Groovy控制台来交互地使用该语言。但是有时候,在命令行上运行Groovy的一行就足够了。-e标志告诉Groovy对刚刚传入的字符串求值。

例如,假设您正在类路径上拾取一个奇怪的JAR。 您可以在类似Unix的系统上键入"echo C L A S S P A T H " 来查看环境变量是否是罪魁祸首。(在 W i n d o w s 系统上设置将获得相似的结果。)如果类路径显示为空,那么讨厌的 J A R 可以在许多其他地方潜入。查看 " CLASSPATH"来查看环境变量是否是罪魁祸首。 (在Windows系统上设置将获得相似的结果。)如果类路径显示为空,那么讨厌的JAR可以在许多其他地方潜入。 查看 " CLASSPATH"来查看环境变量是否是罪魁祸首。(在Windows系统上设置将获得相似的结果。)如果类路径显示为空,那么讨厌的JAR可以在许多其他地方潜入。查看"JAVA_HOME/lib, $JAVA_HOME/lib/ext, 和 $GROOVY_HOME/lib",以查看是否有任何陌生人潜伏。 前面的示例将向您精确显示JRE所看到的内容-您可以从那里查找入侵者。

5.13 在命令行中包含JAR

$ groovy -classpath ~/lib/derbyclient.jar:~/lib/jdom.jar:. db2xml.groovy

如果您有依赖于其他库的脚本,则可以通过JAR列表向groovy传递-classpath开关。 当然,这与从命令行运行Java没有什么不同。 要运行我们的"db2xml.groovy"脚本,该脚本需要访问数据库驱动程序和XML库就不足为奇了。

.groovy/lib目录中自动包括JAR

//on Windows:
mkdir C:\Documents and Settings\UserName\.groovy\lib

//on Unix, Linux, and Mac OS X:
mkdir ~/.groovy/lib

// uncomment the following line in
// $GROOVY_HOME/conf/groovy-starter.conf
# load user specific libraries
load !{user.home}/.groovy/lib/*.jar

您很快就会厌倦了每次都必须在命令行上键入常用的JAR(例如JDBC驱动程序)。 如果在主目录中创建.groovy/lib目录(请不要忘记前导点),则在命令提示符下运行Groovy时,在此目录中找到的所有JAR都会自动包含在CLASSPATH中。 默认情况下,.groovy/lib目录是禁用的; 确保在$GROOVY_HOME/conf/groovy-starter.conf中启用它。

第6章 文件的技巧

Groovy提供了许多处理文件和目录的捷径。 列出文件,复制文件,重命名文件,删除文件-Groovy为所有这些平凡的任务带来了可喜的帮助。 Groovy直接向标准JDK类(例如java.io.File)添加新方法的事实使这些新功能看起来像是该语言的自然组成部分。

健壮的Java构建工具Ant在本章中也有客串出现。 Ant远远超出了标准Java I/O库功能,增加了对相关功能(如批处理操作和ZIP文件)的支持。 即使Ant是用Java编写的,但大多数开发人员熟悉的接口是普遍存在的build.xml文件。 Groovy对XML的本机支持在第7章“解析XML”(第116页)和第8章“编写XML”(第136页)中进行了广泛讨论。在本章中,您将看到一个很好的例子,说明如何在AntBuilder中使用它-所有功能 Ant,没有XML。 一路都是纯代码; 您再也不会以相同的方式查看构建文件了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

爱游泳的老白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值