日々常々

ふつうのプログラマがあたりまえにしたいこと。

groovyコマンドオプション一巡り #gadvent2011

G* Advent Calendar 2011の20日目です。

コマンドラインのGroovy

JavaIDE前提にしている事も有り、JavaからGroovyに入った方はGroovyプラグインを使ったりするので、あまりコマンドラインは使わないかもしれません。現状でもCUIは現役*1ですが、GUIに囲まれて生きて来た人にとって、コマンドで何が出来るかは容易に想像できなかったりします。とりわけ、Windowsしか使った事が無かったりするとその傾向は顕著になります。ええ、私の事です。
でも使えないのは勿体ない。コマンドラインには力がある。なら使えるようになれば良い。そんなわけで今回のエントリになります。

groovy -h

まず groovy -h を叩いて、どんなコマンドラインオプションがあるかを見てみる。

Airof:GAdvent irof$ groovy -h
usage: groovy [options] [args]
options:
  -a,--autosplit <splitPattern>    split lines using splitPattern (default '\s')
                                   using implicit 'split' variable
  -c,--encoding <charset>          specify the encoding of the files
  -classpath <path>                Specify where to find the class files - must
                                   be first argument
  -cp,--classpath <path>           Aliases for '-classpath'
  -D,--define <name=value>         define a system property
  -d,--debug                       debug mode will print out full stack traces
     --disableopt <optlist>        disables one or all optimization elements.
                                   optlist can be a comma separated list with
                                   the elements: all (disables all
                                   optimizations), int (disable any int based
                                   optimizations)
  -e <script>                      specify a command line script
  -h,--help                        usage information
  -i <extension>                   modify files in place; create backup if
                                   extension is given (e.g. '.bak')
  -l <port>                        listen on a port and process inbound lines
                                   (default: 1960)
  -n                               process files line by line using implicit
                                   'line' variable
  -p                               process files line by line and print result
                                   (see also -n)
  -v,--version                     display the Groovy and JVM versions

この数ならいけそうなので、上から順番に行ってみることにします。1ページ超えたりするとやる気無くなりますので丁度いいです。
それぞれの説明はプログラミングGROOVYから拝借させてもらいました。

プログラミングGROOVY

プログラミングGROOVY

groovy -a <splitPattern>

自動splitモード。-nか-pと一緒に使う。各行はpattern(デフォルトは\s)でsplitされ、結果は変数splitに渡される。

なるほど。n/p が出るまで後回しにすれば良いんですね。

groovy -c <charset>

ソースファイルのエンコーディングを指定

おそらくそのままなので、とりあえずUTF-8SJISでファイルを用意。

// utf8.groovy
println 'はろー ゆーてぃーえふえいと'
// sjis.groovy
println 'はろー えすじす'

実行してみる。

Airof:GAdvent irof$ groovy utf8.groovy 
はろー ゆーてぃーえふえいと
Airof:GAdvent irof$ groovy sjis.groovy 
(当然化けた)
Airof:GAdvent irof$ groovy -c sjis utf8.groovy 
(勿論化けた)
Airof:GAdvent irof$ groovy -c sjis sjis.groovy 
はろー えすじす

これでファイルのエンコーディングは大丈夫…けど普段全く意識しなくても良いようにしてくれるし、毎回指定するくらいなら変更してファイル保存すると思う。単発で変換めんどくさい時に使うかなー。

groovy -classpath <path>

クラスパスを指定する

そのまま、クラスの探し先を指定する。IDEJavaクラス出力先を見に行かせれば、Groovyプラグインとか入れなくてもなんかこう上手い事できたりする。かも。

groovy -D <name=value>

システムプロパティを定義

Javaでもあるアレなので、設定してみてみることにする。素直にSystem.properties全部出すといっぱい出てくるので、findAllして出力。

Airof:GAdvent irof$ groovy -Dabc=def -Dzab=deg -e 'println System.properties.findAll{ k,v -> k.contains "ab" }'
[zab:deg, abc:def]

素直にitで受けると HashTable$Entry が入ってくるので、引数2つをとるクロージャにしてる。v使わないから it.key でも構わないんだけども。

groovy -d

デバッグモード。完全なスタックトレースを表示

通常、スタックトレースは一部しか表示されてないようです。大抵は意味が無いから省略されてるんだろうけど、たまにちょっと足りないなーと思う事もありました。……表示するオプションあったらしい。今までcatchしてprintしてた気がする。気のせいだと思いたい。

Airof:GAdvent irof$ groovy -e 'throw new Exception()'
Caught: java.lang.Exception
java.lang.Exception
	at script_from_command_line.run(script_from_command_line:1)
Airof:GAdvent irof$ groovy -d -e 'throw new Exception()'
Caught: java.lang.Exception
java.lang.Exception
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:525)
	at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:77)
	at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:102)
	at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:54)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:182)
	at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:186)
	at script_from_command_line.run(script_from_command_line:1)
	at groovy.lang.GroovyShell.runScriptOrMainOrTestOrRunnable(GroovyShell.java:266)
	at groovy.lang.GroovyShell.run(GroovyShell.java:517)
	at groovy.lang.GroovyShell.run(GroovyShell.java:172)
	at groovy.ui.GroovyMain.processOnce(GroovyMain.java:551)
	at groovy.ui.GroovyMain.run(GroovyMain.java:335)
	at groovy.ui.GroovyMain.process(GroovyMain.java:321)
	at groovy.ui.GroovyMain.processArgs(GroovyMain.java:118)
	at groovy.ui.GroovyMain.main(GroovyMain.java:99)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:601)
	at org.codehaus.groovy.tools.GroovyStarter.rootLoader(GroovyStarter.java:108)
	at org.codehaus.groovy.tools.GroovyStarter.main(GroovyStarter.java:130)

何となくわかった。何となく。きっとたまに使うと思う。

groovy --disableopt <optlist>

指定した最適化処理を抑制する。現時点ではall(すべて)とint(整数関係)のみ。

最適化がかかっているらしくて、それを抑制出来るらしい。最適化のせいで何か嫌な事があった時に使ったりするのでしょうかね。たぶん普段はお世話にならないはず。
オプションの確認のため、 @さんのblog"Exploring Groovy 1.8 Part 1 - そのコード、本当に最適化されてますか?" からコードをお借りしてやってみます。

long time = System.nanoTime()
println fib(30)
println System.nanoTime() - time

int fib(int n) {
	if (n < 2) return n
	return fib(n - 1) + fib(n - 2)
}

ベタに実行。

Airof:GAdvent irof$ groovy fib.groovy 
832040
111103000
Airof:GAdvent irof$ groovy --disableopt all fib.groovy 
832040
432188000

効いてるみたい。結構変わってますね*2
ちなみに all,int 以外のテキトーな文字を指定してみたら、何も指定してないのと同じ(最適化有効)でした。軽く無視されるみたいね。


ついでなので GBench も使ってみた。

@Grab('com.googlecode.gbench:gbench:0.2.2')
import gbench.*

method()

@Benchmark
def method() { println fib(30) }

int fib(int n) {
	if (n < 2) return n
	return fib(n - 1) + fib(n - 2)
}

結果。

Airof:GAdvent irof$ groovy fib_gbench.groovy 
832040
fib_gbench	java.lang.Object method()	user:35327000 system:1423000 cpu:36750000 real:38583000
Airof:GAdvent irof$ groovy --disableopt all fib_gbench.groovy 
832040
fib_gbench	java.lang.Object method()	user:301202000 system:3344000 cpu:304546000 real:326693000

Java6だと自分で出力してるのもGBench使ってるのも同じくらいの時間になってたけど、Java7だとGBenchの最適化有りがやたら小さくなってる。何か間違えてるかなー…。

groovy -e <script>

コマンドラインスクリプトを実行

とっくにやっちゃってましたね!groovyコマンドは通常スクリプトのファイルを受け取るけども、-eを指定することで直接実行出来る。軽く実行する時は楽。

Airof:GAdvent irof$ groovy -e "println 'hello'"
hello

簡単なのは groovy -e で実行する。ワンライナーで厳しい時は groovysh に。もう少し手が込んだら groovyconsole を立ち上げる。さらに複雑になって来たら、スクリプト書いて groovy に戻る。そんな感じ。

groovy -h

ヘルプを表示

最初にやってしまった。

groovy -i <extension>

上書き編集モード。-nか-pと一緒に使い、ファイル内容を処理結果で直接書き換える。拡張子を指定すればバックアップが作られる

n/pと一緒に使うらしいので、これも後回し。

groovy -l <port>

listenモード。指定ポートで接続を待ち、受信行を変数lineに渡す。

簡易サーバが作れます。地味に便利。
SimpleWebServer*3とか動かしてみるのが手っ取り早い感じ。適当にリクエスト捌けるので、ちょっとした遊びにも使えるかも。いつぞやのGroovyイン・アクション読書会でこれとsayコマンド使って遊んでた人が居た気がします。
クライアントとサーバ別々に開発する時とか、サーバ代わりに軽くスクリプト書いてモック的に使うのもありかもしれません。

これでファイルの内容をそのまま書き出します。ポートを省略すると 1960 が使われます。

groovy -l -e "println new File('hoge.txt').text; return 'success'"

HTTPだとレスポンスヘッダを付けたりしなきゃいけないのですが、この例だと hoge.txt の中に直接書いてしまえば要件は満たせました。非常に大雑把ですけど、その大雑把さが良い感じです。
流石にこれだけだと有り難みは少ないですが、スクリプトファイルを実行するようにして簡単な処理を書いていけば、そのうちサーバ処理本体が出来上がります。一石二鳥…それはないか。

groovy -n

行処理モード。行ごとにループし、各行を変数lineに渡す

-p のほうがわかりよいので、次へ。

groovy -p

同上だが各行の実行結果が自動的に出力される

普通に処理するのが -n で、これで何かを出力したかったら print を自分で書かなきゃいけない。実行結果をそのまま println するのが -p っぽい。実行結果は p に渡したものの戻り。groovysh とか使ってると実行後に表示されるもの(下の ===> の次に表示されるもの)みたいです。

groovy:000> 1           
===> 1
groovy:000> 1;2;3;
===> 3
groovy:000> 1;return 2;3;
===> 2

a/i/n/p は「perlrubyコマンドに由来している」とプログラミングGROOVYに書いているのですが、これらのコマンドをあまり使わない私にとっては初めて見るも同然。なのでどう使うか想像も…出来ますけどね。テキストファイル処理するのに便利そうなので、明日から使いましょうそうしましょう。


とりあえずカンで使ってみようと思う。まず読み込むファイルを用意。

head1,head2,head3
data1-1,data1-2,data1-3
data2-1,data2-2,data2-3
data3-1,data3-2,data3-3
data4-1,data4-2,data4-3

簡単なCSVってことで。これをdata.txtって名前で保存して。

全件出力。

Airof:GAdvent irof$ groovy -p -e "line" data.txt
head1,head2,head3
data1-1,data1-2,data1-3
data2-1,data2-2,data2-3
data3-1,data3-2,data3-3
data4-1,data4-2,data4-3

どうしよう、全く面白くない…。とりあえず -a を使ってみましょうか。

Airof:GAdvent irof$ groovy -a "," -pe "split" data.txt
[Ljava.lang.String;@5b63280a
[Ljava.lang.String;@4ad9d765
[Ljava.lang.String;@23cc4e47
[Ljava.lang.String;@3a2729ad
[Ljava.lang.String;@61213aae

String配列になるらしい。普通のsplitなんでしょう。相変わらず配列のtoStringは役に立たないな…。

collectすればListになる。だからどうした?少なくとも読めるようになるよ。素直に Arrays.asList でもやっとけと思わなくもない。

Airof:GAdvent irof$ groovy -a "," -pe "split.collect{it}" data.txt
[head1, head2, head3]
[data1-1, data1-2, data1-3]
[data2-1, data2-2, data2-3]
[data3-1, data3-2, data3-3]
[data4-1, data4-2, data4-3]

ファイルを編集出来ると言う -i を使ってみよう。バックアップは .bak で。

Airof:GAdvent irof$ groovy -i .bak -pe "line + ',xxx'" data.txt
Airof:GAdvent irof$ cat data.txt
head1,head2,head3,xxx
data1-1,data1-2,data1-3,xxx
data2-1,data2-2,data2-3,xxx
data3-1,data3-2,data3-3,xxx
data4-1,data4-2,data4-3,xxx
Airof:GAdvent irof$ cat data.txt.bak
head1,head2,head3
data1-1,data1-2,data1-3
data2-1,data2-2,data2-3
data3-1,data3-2,data3-3
data4-1,data4-2,data4-3

だいたい思った通りになる。


バックアップはリネーム的な挙動っぽい。

Airof:GAdvent irof$ chmod +x data.txt
Airof:GAdvent irof$ ls -l
-rwxr-xr-x   1 irof  staff    134 12 20 03:19 data.txt
Airof:GAdvent irof$ groovy -i .bak -pe "line + ',yyy'" data.txt
Airof:GAdvent irof$ ls -l
-rw-r--r--   1 irof  staff    154 12 20 03:21 data.txt
-rwxr-xr-x   1 irof  staff    134 12 20 03:19 data.txt.bak

うっかり -n するとファイルの内容全部消えて笑えなかった。出力した結果のファイルが作られるみたい。標準出力先をファイルにしてる感じですね。
なんにせよ、これでテキストファイルをGroovyで楽に弄れるようになれた、と思う。前まで File に write してました。それでも十分楽なんですけどね。

groovy -v

GroovyとJavaのバージョンを表示

このエントリのバージョンは

Airof:GAdvent irof$ groovy -v
Groovy Version: 1.8.4 JVM: 1.7.0-ea

でした。

*1:と言うか、自動化とかちょっとしたツールとか考えると自然とCUIになります。その方が制御が効いて楽ですので。

*2:Java7( OpenJDK Runtime Environment (build 1.7.0-ea-b215) )でやってます。Java6( Java(TM) SE Runtime Environment (build 1.6.0_29-b11-402-11M3527) ) だと最適化有効だと同じくらいで、最適化無効は倍くらいかかってました。厳密な計測じゃないので参考…にもならないか。

*3:これ通常どこからどうリンク辿ったら行けるんだろう… http://groovy.codehaus.org/ からはリンク切れちゃってるっぽいし。