よく使いそうなGroovy使い方メモ

  • 107
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

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 でメソッド一覧を取得できる。

コマンドライン引数を取得する

参考

test.groovy
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 でまとめて取得できる。

Process (Groovy JDK)

コマンドライン引数を解析する

参考

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 オプションが指定されました

基本は次の順序で使用する。

  1. CliBuilder のインスタンスを生成する。
  2. オプションを定義する。
  3. コマンドライン引数を解析する。
  4. 解析結果を参照する。

解析でエラーと判定された場合の動作

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, '必須オプション'
}

requiredtrue を指定すると、そのオプションは必須入力になる。

オプションに長い名前の別名を定義する

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

args1 を指定すると、オプションに必須パラメータを定義できる。

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

optionalArgtrue を指定すると、オプションのパラメータ指定が任意になる。

オプションパラメータを 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
Hoge.groovy
def class Hoge {
    def method() {
        println 'hoge'
    }
}
Fuga.groovy
package sub

def class Fuga {
    def method() {
        println 'fuga'
    }
}
Main.groovy
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

参考

検索

複数行取得

eachRow()メソッド
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]}"
}
query()メソッド
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 が渡される。

rows()メソッド
def list = sql.rows('SELECT * FROM TEST_TABLE')

list.each { println it }

rows() メソッドは、結果をすべて読み込んでその結果を返す。

単一行取得

firstRow()メソッド
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 を使ってテキスト表現を取得する。
単純にMarkupBuilderのtoString()を実行した場合
<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 で設定ファイルを作成する

参考

設定ファイルのロード

config.groovy
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
main.groovy
evaluate(new File('./sub.groovy'))

println 'main.groovy'
println sub
sub.groovy
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() にクロージャを渡すと、渡したクロージャが新しいスレッドで実行される。

よく参照しそうな Groovy JDK の Javadoc

  • この記事は以下の記事からリンクされています
  • Gradle使い方メモからリンク