Groovy のスクリプトを書いてて、よく利用しそうな実装方法を随時メモしていく。
演算子
エルビス演算子で変数を初期化する
参考
a = 'A' ?: 'a'
b = [1, 2] ?: 'b'
c = -1 ?: 'c'
d = true ?: 'd'
println "a=${a}, b=${b}, c=${c}, d=${d}"
w = '' ?: 'w'
x = [] ?: 'x'
y = 0 ?: 'y'
z = false ?: 'z'
println "w=${w}, x=${x}, y=${y}, z=${z}"
a=A, b=[1, 2], c=-1, d=true
w=w, x=x, y=y, z=z
<値A> ?: <値B>
で、 <値A>
が真と評価される値の場合はそれ自身が、偽と判断される場合は <値B>
が評価される。
with を使ってオブジェクトのプロパティを初期化する
def hoge = new Hoge()
hoge.with {
id = 10
name = 'hoge'
}
println "id=${hoge.id}, name=${hoge.name}"
def class Hoge {
def name
def id
}
id=10, name=hoge
-
with()
メソッドにクロージャを渡すと、その中でwith()
メソッドを呼び出したオブジェクトのフィールドに直接アクセスできる。 - あくまでクロージャーなので、メソッドの実行やロジックの記述も可能。
プロパティ・メソッド一覧を取得する
import java.nio.file.Paths
def obj = Paths.get('hoge.txt')
println '[properties]'
obj.properties.each { property ->
println property
}
println()
println '[methods]'
obj.metaClass.methods.each { method ->
println method
}
[properties]
class=class sun.nio.fs.WindowsPath
absolute=false
pathForPermissionCheck=hoge.txt
fileSystem=sun.nio.fs.WindowsFileSystem@6f9e305d
nameCount=1
unc=false
fileName=hoge.txt
pathForExceptionMessage=hoge.txt
root=null
absolutePath=D:\tmp\groovy\hoge.txt
empty=false
pathForWin32Calls=D:\tmp\groovy\hoge.txt
parent=null
[methods]
public boolean java.lang.Object.equals(java.lang.Object)
public final native java.lang.Class java.lang.Object.getClass()
public native int java.lang.Object.hashCode()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
public java.lang.String java.lang.Object.toString()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final boolean sun.nio.fs.AbstractPath.endsWith(java.lang.String)
public final java.util.Iterator sun.nio.fs.AbstractPath.iterator()
(以下略)
properties
でプロパティ一覧を、 metaClass.methods
でメソッド一覧を取得できる。
コマンドライン引数を取得する
参考
println "args.class = ${args.class}"
println "args = ${args}"
>groovy test.groovy hoge fuga
args.class = class [Ljava.lang.String;
args = [hoge, fuga]
args
という String の配列変数から取得できる。
コマンドの実行
参考
基本
'notepad'.execute()
String クラスの execute()
メソッドを使うと、コマンドが実行できる。
コマンドは非同期で実行されるので、コマンドを実行したら終了を待つこと無く次のステップに進む。
コマンドの終了を待機する
'notepad'.execute().waitFor()
println 'done'
Process
クラスの waitFor()
メソッドでコマンドの終了を待機できる。
上記実装の場合、メモ帳が閉じられたら println 'done'
が実行される。
コマンドのリターンコードを取得する
exitValue = 'notepad'.execute().waitFor()
println "exitValue=${exitValue}"
exitValue=0
waitFor()
メソッドの戻り値が、コマンドのリターンコードになっている。
waitFor() | Process (Java Platform SE 6)
コマンドプロンプトのコマンドを実行する
'cmd /c dir'.execute()
cmd /c <コマンド>
で実行できる。
実行したコマンドが標準出力に出力した結果を取得する
println 'cmd /c echo /?'.execute().text
メッセージを表示したり、コマンド エコーの ON と OFF を切り替えます。
ECHO [ON | OFF]
ECHO [メッセージ]
現在のエコー設定を表示するには、パラメーターを指定せずに ECHO と入力して
ください。
getText() というメソッドがあり、コマンドが出力した結果を String でまとめて取得できる。
コマンドライン引数を解析する
参考
Groovy には Apache Commons CLI を簡単に利用するための CliBuilder というクラスが標準で組み込まれていて、これを使えば簡単にコマンドライン引数の解析ができる。
基本
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a 'A option'
}
def arguments = ['-a']
def options = cli.parse(arguments)
if (options.a) {
println('a オプションが指定されました')
}
a オプションが指定されました
基本は次の順序で使用する。
-
CliBuilder
のインスタンスを生成する。 - オプションを定義する。
- コマンドライン引数を解析する。
- 解析結果を参照する。
解析でエラーと判定された場合の動作
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a required: true, 'A option'
}
def arguments = ['-b']
def options = cli.parse(arguments)
println "options = ${options}"
error: Missing required option: a
usage: hoge
-a A option
options = null
parse()
メソッドでコマンドライン引数を解析した結果、エラーと判定された場合、エラー内容と usage が表示される(上記例の場合、必須指定の -a
オプションが指定されていない)。
例外はスローされず、 parse()
メソッドの戻り値が null になる。
usage を表示する
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a 'A option'
}
cli.usage()
usage: hoge
-a A option
usage()
メソッドを実行すると、 usage が表示される。
オプションを必須入力にする
cli.with {
a required: true, '必須オプション'
}
required
に true
を指定すると、そのオプションは必須入力になる。
オプションに長い名前の別名を定義する
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a longOpt: 'aaa', 'A option'
}
def arguments = ['--aaa']
def options = cli.parse(arguments)
if (options.a) {
println 'a オプションが指定されました。'
}
cli.usage()
a オプションが指定されました。
usage: hoge
-a,--aaa A option
longOpt
でオプションの別名を定義できる。
オプションにパラメータを定義する
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a args: 1, 'A option'
}
def arguments = ['-a', 'a option value']
def options = cli.parse(arguments)
if (options.a) {
println "a オプション に ${options.a} が渡されました。"
}
cli.usage()
a オプション に a option value が渡されました。
usage: hoge
-a <arg> A option
args
に 1
を指定すると、オプションに必須パラメータを定義できる。
usage で表示するオプションの名前を変更する
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a args: 1, argName: 'a parameter', 'A option'
}
cli.usage()
usage: hoge
-a <a parameter> A option
argName
で usage に表示するオプションパラメータの名前を指定できる。
オプションのパラメータを任意指定にする
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a args: 1, optionalArg: true, 'A option'
}
def arguments = ['-a']
def options = cli.parse(arguments)
if (options.a) {
println "options.a = ${options.a}"
}
options.a = true
optionalArg
に true
を指定すると、オプションのパラメータ指定が任意になる。
オプションパラメータを key=value 形式にする
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a args: 2, valueSeparator: '=', 'A option'
}
def arguments = ['-a', 'hoge=HOGE']
def options = cli.parse(arguments)
if (options.a) {
println "options.a = ${options.a}"
println "options.as = ${options.as}"
}
options.a = hoge
options.as = [hoge, HOGE]
valueSeparator
を指定すると、オプションのパラメータを key=value
のような形式で指定できる。
このとき、 args
には 2
を指定しなければならない。
オプション名だけでパラメータの値を取得すると、 valueSeparator
で指定した文字の右側の値だけが取得できる。
オプション名の末尾に s
を付けて値を取得すると、 List が返される。
List には、 valueSeparator
でパラメータを split した結果が設定されている。
つまり、次のような使い方もできる。
def cli = new CliBuilder(usage: 'hoge')
cli.with {
a args: 3, valueSeparator: ',', 'A option'
}
def arguments = ['-a', 'hoge,fuga,piyo']
def options = cli.parse(arguments)
if (options.a) {
println "options.as = ${options.as}"
}
options.as = [hoge, fuga, piyo]
ただし、パラメータの途中にスペースがあるとうまく解析できないので注意。
例えば、 -a hoge, fuga, piyo
としてしまうと、 -a
のオプションは hoge,
だけになってしまう。
長い名前だけでオプションを定義する
def cli = new CliBuilder(usage: 'hoge')
cli.with {
_ longOpt: 'aaa', 'A option'
_ longOpt: 'bbb', 'B option'
}
cli.usage()
usage: hoge
--aaa A option
--bbb B option
_
という名前でオプションを定義して、 longOpt
を指定すれば、長い名前だけでオプションを定義できる。
引数を取得する
def cli = new CliBuilder()
cli.with {
a 'a option'
}
def cmd = cli.parse(['-a', 'hoge', 'fuga', 'piyo'])
println cmd.arguments()
[hoge, fuga, piyo]
-
arguments()
メソッドでオプション以外の引数を list で取得できる。
クラス単位でファイルを分割する
|-Main.groovy
|-Hoge.groovy
`-sub/
`-Fuga.groovy
def class Hoge {
def method() {
println 'hoge'
}
}
package sub
def class Fuga {
def method() {
println 'fuga'
}
}
def hoge = new Hoge()
hoge.method()
> groovy Main
hoge
- ファイル名と同じ名前のクラスを定義する。
- パッケージは、クラスパスでしていしたディレクトリを起点として定義する(デフォルトは、実行ディレクトリにクラスパスが通る)。
ファイル(フォルダ)検索
指定したフォルダ直下のみ対象
// 全フォルダ・全ファイル
new File('path/to/dir').eachFile { println it.name }
// ファイルのみ
import groovy.io.FileType
new File('path/to/dir').eachFile(FileType.FILES) { println it.name }
// フォルダのみ
new File('path/to/dir').eachDir { println it.name }
// 名前が指定した正規表現に一致するファイルのみ
import groovy.io.FileType
new File('path/to/dir').eachFileMatch(FileType.FILES, ~/[a-z]+.txt/) { println it.name }
// 名前が指定した正規表現に一致するフォルダのみ
new File('path/to/dir').eachDirMatch(~/[a-z]+/) { println it.name }
指定したフォルダ以下再帰的に検索
// 全フォルダ・全ファイル
new File('path/to/dir').eachFileRecurse { println it.name }
// ファイルのみ
import groovy.io.FileType
new File('path/to/dir').eachFileRecurse(FileType.FILES) { println it.name }
// フォルダのみ
import groovy.io.FileType
new File('path/to/dir').eachFileRecurse(FileType.DIRECTORIES) { println it.name }
参考
ファイル読み込み
全行読み込み
def text = new File('hoge.txt').text
// 文字コード指定
def text = new File('hoge.txt').getText('Shift_JIS')
1行づつ読み込み
new File('hoge.txt').eachLine { println it }
// 文字コード指定
new File('hoge.txt').eachLine('UTF-8') { println it }
ファイル書き込み
追記
// append()メソッド
new File('hoge.txt').append('追記')
// append()メソッド:文字コード指定
new File('hoge.txt').append('ほげ', 'Shift_JIS')
// <<演算子
new File('hoge.txt') << 'append text'
上書き
// write()メソッド
new File('hoge.txt').write('上書き')
// write()メソッド:文字コード指定
new File('hoge.txt').write('ぴよ', 'Shift_JIS')
// textプロパティ
new File('hoge.txt').text = 'append'
// setText()メソッド:文字コード指定
new File('hoge.txt').setText('ふが', 'Shift_JIS')
BufferedWriter の取得
def writer = new File('hoge.txt').newWriter()
この場合は 上書き モードで取得される。
newWriter()
の引数に true
を渡せば 追記モード になる。
def writer = new File('hoge.txt').newWriter('UTF-8')
// 追記モード
def writer = new File('hoge.txt').newWriter('UTF-8', true)
withWriter() でクローズ処理を省略
// デフォルト文字コードで上書き
new File('hoge.txt').withWriter { writer ->
writer << 'aaa'
}
// 文字コードを指定して上書き
new File('hoge.txt').withWriter('UTF-8') { writer ->
writer << 'bbb'
}
// デフォルト文字コードで追記
new File('hoge.txt').withWriterAppend { writer ->
writer << 'ccc'
}
// 文字コードを指定して追記
new File('hoge.txt').withWriterAppend('Shift_JIS') { writer ->
writer << 'ddd'
}
正規表現
マッチした部分文字列だけを抽出する
def str = ('hoge 123 fuga' =~ /[0-9]+/)[0]
println "str = ${str}"
str = 123
データベース操作
参考
接続
import groovy.sql.Sql
def url = 'jdbc:mysql://localhost/test'
def user = 'test'
def pass = 'test'
def driver = 'com.mysql.jdbc.Driver'
def sql = Sql.newInstance(url, user, pass, driver)
各 RDBMS の URL とドライバ名
データベース製品 | URL | ドライバ(クラス名) |
---|---|---|
Oracle | jdbc:oracle:thin:@<host>:<port>:<database> |
oracle.jdbc.driver.OracleDriver |
MySQL | jdbc:mysql://<host>:<port>/<database> |
com.mysql.jdbc.Driver |
PostgreSQL | jdbc:postgresql://<host>:<port>/<database> |
org.postgresql.Driver |
HSQLDB | jdbc:hsqldb:hsql://<host>:<port> |
org.hsqldb.jdbc.JDBCDriver |
参考
- (補足)Oracleへの接続 | TECHSCORE(テックスコア)
- MySQL :: MySQL 5.1 リファレンスマニュアル :: 24.4.5.1 JDBC の基本コンセプト
- jdbc経由でPostgreSQLにアクセスする - いろいろ書いてみる - Yahoo!ブログ
- [ThinkIT] 第7回:データベースの利用 (1/3)
検索
複数行取得
sql.eachRow('SELECT * FROM TEST_TABLE') { println it }
// カラム名を指定して値を取得
sql.eachRow('SELECT * FROM TEST_TABLE') {
println "ID=${it.id}, CODE=${it.code}, VALUE=${it.value}"
}
// インデックスを指定して値を取得
sql.eachRow('SELECT * FROM TEST_TABLE') {
println "ID=${it[0]}, CODE=${it[1]}, VALUE=${it[2]}"
}
sql.query('SELECT * FROM TEST_TABLE') { rs ->
while (rs.next()) {
println "ID=${rs.getInt('ID')}, CODE=${rs.getString('CODE')}, VALUE=${rs.getString('VALUE')}"
}
}
query()
メソッドを使うと、クロージャに ResultSet が渡される。
def list = sql.rows('SELECT * FROM TEST_TABLE')
list.each { println it }
rows()
メソッドは、結果をすべて読み込んでその結果を返す。
単一行取得
def result = sql.firstRow 'SELECT COUNT(*) FROM TEST_TABLE'
println result.'COUNT(*)'
更新
def id = 10
def code = 'hoge'
def value = 'HOGE'
sql.execute """
INSERT INTO TEST_TABLE (
ID
,CODE
,VALUE
) VALUES (
${id}
,${code}
,${value}
)
"""
GString
を使えば、勝手に PreparedStatement を使ってくれる。
トランザクション
sql.withTransaction {
sql.execute "DELETE FROM TEST_TABLE WHERE ID=4"
throw new RuntimeException("test runtime exception")
}
withTransaction
に渡したクロージャ内が1トランザクションで実行される。
クロージャ内で例外がスローされると、トランザクションがロールバックされる。
ただし、スローされる例外によって以下のように若干動作が異なる。
スローされる例外 | 動作 |
---|---|
RuntimeException 及び SQLException
|
即座にロールバックされる |
Exception |
ロールバックもコミットもされない |
RuntimeException
及び SQLException
がスローされると、その時点でロールバックが実行される。
しかし、ただの Exception
がスローされた場合はロールバックもコミットもされない。
そのままスクリプトが終了した場合、変更は DB に反映されない。
つまり、 Exception
がスローされても、手動で sql.connection.commit()
としてやれば変更をコミットできる(使いドコロは不明だけど)。
MarkupBuilder で XML, HTML を生成する方法
参考
基本
import groovy.xml.MarkupBuilder
def writer = new StringWriter()
new MarkupBuilder(writer).div {
b('MarkupBuilder!!')
}
println writer
<div>
<b>MarkupBuilder!!</b>
</div>
-
MarkupBuilder
を new して、あとはタグでマークアップしているかのように構造を記述する。 -
MarkupBuilder
のインスタンスを単純にtoString()
すると、末尾によくわからないタグ名がくっついてくるので、StringWriter
を使ってテキスト表現を取得する。
<div>
<b>MarkupBuilder!!</b>
</div>div
なんか、最後に div
という文字列がくっついてくる。
属性を指定する
// 基本
new MarkupBuilder().input(id: 'hoge')
/* 出力結果
<input id='hoge' />
*/
// 予約語とかぶる場合は文字列で指定する
new MarkupBuilder().input('class': 'fuga')
/* 出力結果
<input class='fuga' />
*/
子タグを指定する
new MarkupBuilder(writer).div {
input(id: 'hoge')
}
/* 出力結果
<div>
<input id='hoge' />
</div>
*/
内容を指定する
// 内容だけ
new MarkupBuilder().div('content')
/* 出力結果
<div>content</div>
*/
// 属性も指定する
new MarkupBuilder().div('class': 'fuga', 'content', id: 'hoge')
/* 出力結果
<div class='fuga' id='hoge'>content</div>
*/
// 子タグと一緒に指定する
new MarkupBuilder().div('content') {
p('paragraph')
}
/* 出力結果
<div>content
<p>paragraph</p>
</div>
*/
単独文字列の引数をタグ宣言に渡すと、それがタグの内容になる。
属性をダブルクォーテーションで括る
デフォルトだと、属性の値はシングルクォーテーションで括られる。
ダブルクォーテーションで括るようにするには、 setDoubleQuotes(boolean)
メソッドで true
を設定する。
def builder = new MarkupBuilder()
builder.setDoubleQuotes(true)
builder.input(id: 'hoge') // => <input id="hoge" />
ロジックを書いて動的に構造を定義する
bool = true
new MarkupBuilder().div {
if (bool) {
p('true!!')
} else {
p('false!!')
}
}
<div>
<p>true!!</p>
</div>
- クロージャなので、ロジックを書くことができる。
ConfigSlurper で設定ファイルを作成する
参考
設定ファイルのロード
hoge {
aaa = 'AAA'
fuga {
bbb = 'BBB'
ccc = 'CCC'
}
}
def config = new ConfigSlurper().parse(new File('config.groovy').toURL())
println config
[hoge:[aaa:AAA, fuga:[bbb:BBB, ccc:CCC]]]
- groovy のクロージャーを使って階層構造で宣言したファイルを設定ファイルとして読み込むことができる。
出力
def config = new ConfigObject()
config.test = [1, 2, 3]
new File('out.groovy').withWriter { writer ->
config.writeTo(writer)
}
別の groovy スクリプトファイルを実行する
参考
|- main.groovy
`- sub.groovy
evaluate(new File('./sub.groovy'))
println 'main.groovy'
println sub
println 'sub.groovy'
sub = 'SUB'
sub.groovy
main.groovy
SUB
-
evaluate()
で指定した groovy ファイルを実行できる。 - 実行した groovy ファイルの中で定義した変数を参照できる。
- 簡易的な設定ファイルとして便利そう
コマンドラインでパスワードの入力を求める
Console console = System.console()
char[] chars = console.readPassword('please type password > ')
String pass = new String(chars)
println pass
java コマンドで実行する
Groovy はインストールされていないけど Java はインストールされている環境で Groovy のスクリプトを動かしたくなったとき用。
最低でも以下の jar ファイルが必要。
%GROOVY_HOME%/embeddable/groovy-all-x.x.x.jar
他に依存する jar がなければ以下のコマンドで実行できる。
>java -jar groovy-all-x.x.x.jar script.groovy
他に依存する jar が存在する場合は、以下のコマンドで実行する。
>java -cp groovy-all-x.x.x.jar;<依存する jar のパス...> groovy.ui.GroovyMain script.groovy
※-jar オプションで jar ファイルを実行する場合、クラスパスをコマンドラインから指定できない仕様になっているため。
commons の CLI を利用している場合は依存する jar として別途クラスパスを指定しなければいけないので注意。
参考
ヒアドキュメント
基本
println """hoge
fuga
piyo
"""
hoge
fuga
piyo
- シングルクォーテーション3つ、またはダブルクォーテーション3つで括ることで、ヒアドキュメントが書ける。
- ダブルクォーテーションの方は GString 扱いなので、変数の展開が可能。
先頭の改行を無視させる
println """\
hoge
fuga
piyo
"""
hoge
fuga
piyo
- ヒアドキュメントの先頭に
\
を入れることで、先頭の改行を無視できる。
コードのインデントで生まれるスペースを無視させる
println """\
|hoge
|fuga
|piyo
|""".stripMargin()
hoge
fuga
piyo
-
stripMargin()
メソッドを実行することで、パイプ|
から左のスペースを無視させることができる。
参考
スレッド処理を実行する
println 'main thread : ' + Thread.currentThread().name
Thread.start {
println 'new thread : ' + Thread.currentThread().name
}
main thread : Thread-15
new thread : Thread-16
Thread.start()
にクロージャを渡すと、渡したクロージャが新しいスレッドで実行される。