Vagrant で VirtualBox 上の CentOS 7 へ固定 IP を設定

はじめに

Vagrant を使って VirtualBox 上で CentOS 7 を起動する際に、固定 IP を設定しようとするとエラーが発生しました。

Vagrantfile
・・・
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
    config.vm.box = "centos7"
    # ゲスト OS 間の通信用に固定 IP アドレスを設定
    config.vm.network "private_network", ip: "192.168.100.11", virtualbox__intnet: "intnet"
end

CentOS 7 の Box を centos7 という名称で Vagrant へ登録済みです。

なお、virtualbox__intnet の設定が無いとホスト OS と通信するための IP アドレス設定 (ホストオンリーアダプター) になるようです。 (この場合、その IP でゲスト OS 間通信はできない模様)

vagrant up 時のエラー内容
> vagrant up

Bringing machine 'default' up with 'virtualbox' provider...
==> default: Importing base box 'centos7'...
・・・
==> default: Configuring and enabling network interfaces...
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

ARPCHECK=no /sbin/ifup eth1 2> /dev/null

Stdout from the command:

ERROR    : [/etc/sysconfig/network-scripts/ifup-eth] Device eth1 does not seem to be present, delaying initialization.


Stderr from the command:

CentOS 7 自体の起動は成功しており (SSH でログイン可能)、固定 IP アドレスの設定に失敗しています。

エラーの原因

原因は下記の通りです。

  • CentOS 7 では NIC の名称がデフォルトで eth<番号> (例 eth0) では無くなっている
  • VagrantRedHat 系 OS 設定用のプラグインでは今のところ eth<番号> 以外の NIC 名を処理できない

要するに、VirtualBox 上で CentOS 7 を実行すると NIC 名が下記のようになりますが、VagrantRedHat 系 OS のネットワーク設定を担うスクリプトplugins/guests/redhat/cap/configure_networks.rb) では、今のところ下記 NIC 名に対応していません。

  • enp0s3 (アダプター1)
  • enp0s8 (アダプター2)

実は、Fedora のネットワーク設定を担うスクリプトplugins/guests/fedora/cap/configure_networks.rb) では biosdevname -d コマンドを使って NIC 名を取得しており、上記 NIC 名に対応できているようです。

回避方法

本件のエラーを回避するには下記のような方法が考えられます。 (プラグインを用いる等、他の方法も色々あると思います)

  • (a) CentOS の設定を変更して古い NIC 名 (eth<番号>) を使うようにする
  • (b) RedHat 用の configure_networks.rb へ Fedora 用の configure_networks.rb の処理内容を適用する
  • (c) プロビジョニングで固定 IP を設定する

個人的に、本件は Vagrant 側で対応すべき問題だと思うので、今回は (b) と (c) の対応方法をご紹介します。

(b) RedHat 用の configure_networks.rb へ Fedora 用の configure_networks.rb の処理内容を適用する

Fedora の configure_networks.rb の実装内容を RedHat の configure_networks.rb へ適用する方法です。

configure_networks.rb の内容を書き換えてしまうので、あまり望ましい方法では無いかもしれませんが、対応手順は下記のようになります。

  • (1) plugins/guests/redhat/cap/configure_networks.rb のバックアップをとる
  • (2) Fedora 用の configure_networks.rb (plugins/guests/fedora/cap/configure_networks.rb) を RedHat 用の configure_networks.rb (plugins/guests/redhat/cap/configure_networks.rb) へ上書きコピー
  • (3) (2) で更新した RedHat 用の configure_networks.rb ファイルを編集し、モジュール名を GuestFedora から GuestRedHat へ変更
plugins/guests/redhat/cap/configure_networks.rb の内容
・・・
module VagrantPlugins
  module GuestRedHat
  ・・・

なお、Windows 用の Vagrant の場合は embedded\gems\gems\vagrant-1.6.3 に plugins ディレクトリがあります。

Fedora 用の configure_networks.rb を適用して問題ないかを十分検証したわけではありませんが、一応 vagrant up でエラーが発生せず固定 IP アドレスを設定できました。

実行例
> vagrant up

・・・
==> default: Configuring and enabling network interfaces...
==> default: Mounting shared folders...
・・・

enp0s8 へ IP アドレスが設定されている事を確認。

> vagrant ssh
Last login: Mon Aug 25 06:50:04 2014 from 10.0.2.2
[vagrant@localhost ~]$ ip addr
・・・
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP
qlen 1000
    ・・・
    inet 192.168.100.11/24 brd 192.168.100.255 scope global enp0s8
    ・・・

(c) プロビジョニングで固定 IP を設定する方法

Vagrant のプロビジョニング機能を使い ip addr add コマンド等で IP アドレスを設定するだけなので、(b) に比べると安全な方法だと思います。

まずは、Vagrantfile へ下記のように provision の定義を追加し、:inline に対して固定 IP アドレスを enp0s8 へ設定し有効化するコマンドを記載します。

Vagrantfile
・・・
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
    config.vm.box = "centos7"

    config.vm.network "private_network", ip: "192.168.100.11", virtualbox__intnet: "intnet"
    # プロビジョニングによる固定 IP の設定
    config.vm.provision :shell, :inline => "sudo ip addr add 192.168.100.11/24 dev enp0s8; sudo ip link set enp0s8 up"
end

vagrant up のネットワーク設定でエラーが発生し途中終了するので、その後に vagrant provision を実行すると固定 IP アドレスを設定します。

実行例1
> vagrant up

・・・
==> default: Configuring and enabling network interfaces...
The following SSH command responded with a non-zero exit status.
Vagrant assumes that this means the command failed!

ARPCHECK=no /sbin/ifup eth1 2> /dev/null

Stdout from the command:

ERROR    : [/etc/sysconfig/network-scripts/ifup-eth] Device eth1 does not seem to be present, delaying initialization.
・・・

vagrant provision でプロビジョニング機能を実行します。

> vagrant provision
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: RTNETLINK answers: File exists

enp0s8 へ IP アドレスが設定されている事を確認。

> vagrant ssh
・・・
[vagrant@localhost ~]$ ip addr
・・・
3: enp0s8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    ・・・
    inet 192.168.100.11/24 brd 192.168.100.255 scope global enp0s8
    ・・・

vagrant up でエラーを発生させないようにする

vagrant up の後に vagrant provision を別途実行するのが面倒な場合。

plugins/guests/redhat/cap/configure_networks.rb を下記のように編集し、ifup が失敗してもエラーを発生させないようにすれば vagrant up 時にプロビジョニングも適用できるようになります。 (ただし、マウントエラーのような他のエラーを発生させない事が前提)

plugins/guests/redhat/cap/configure_networks.rb 編集例 - ifup の実行処理へ error_check: false を追加 (61 行目)
machine.communicate.sudo("ARPCHECK=no /sbin/ifup eth#{interface} 2> /dev/null", error_check: false)
実行例2 - configure_networks.rb を編集した場合
> vagrant up --provision

Bringing machine 'default' up with 'virtualbox' provider...
・・・
==> default: Configuring and enabling network interfaces...
・・・
==> default: Running provisioner: shell...
    default: Running: inline script
==> default: RTNETLINK answers: File exists

なお、vagrant up でプロビジョニングが適用されるのは初回起動時のみのようですので、2回目以降は vagrant up --provision でプロビジョニングを強制適用して起動する必要があります。

成功するまで次を試すような処理へ Either モナドを適用 - FunctionalJava

成功するまで次の処理を試していくような処理に対して Either モナドを適用してみました。

使用した環境は下記の通りです。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140825/

はじめに

Either モナドは 2つの異なる値 (Left と Right) を扱う場合に使用し、一般的には失敗(エラー)を伴う処理に対して下記のような使い方をします。

Leftの値 Rightの値
失敗時のエラー内容(例外) 成功時の値

ただし今回は、下記のように処理が成功するまで元の値が Left へ保持されるような使い方をしてみました。

Leftの値 Rightの値
元の値 成功時の値

具体的には、下記のような処理を試します。

  • 実行時引数で指定した日付文字列に対して、Date オブジェクトへのパースが成功するまで次のパース処理を試していく

Either モナドを使わなかった場合

まずは Either モナドを使わず、普通に Java で実装してみました。

下記 (1) ~ (5) のパース処理を順に試して、例外が発生せず null では無い値を返した時点でパース処理を終了します。

  • (1) ISO-8601 タイムゾーン無しの日付文字列(例 2014-08-25T13:20:00)をパース
  • (2) ISO-8601 の日付文字列(例 2014-08-25T13:20:00+09:00)をパース
  • (3) ISO-8601 タイムゾーン付きの日付文字列(例 2014-08-25T13:20:00+09:00[Asia/Tokyo])をパース
  • (4) "yyyy-MM-dd HH:mm:ss" をパース
  • (5) "now" の場合に Date オブジェクトを返す

java.time のクラスを使う必要も無かったのですが、ついでに試してみました。

DateParse.java
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.function.Function;

public class DateParse {
    public static void main(String... args) {
        // SimpleDateFormat を使ったパース
        Function<String, Function<String, Date>> simpleDate = df -> s -> {
            try {
                return new SimpleDateFormat(df).parse(s);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };

        Date res = parseDate(
            args[0],
            s -> Date.from(LocalDateTime.parse(s).toInstant(ZoneOffset.UTC)), //(1)
            s -> Date.from(OffsetDateTime.parse(s).toInstant()), // (2)
            s -> Date.from(ZonedDateTime.parse(s).toInstant()), // (3)
            simpleDate.apply("yyyy-MM-dd HH:mm:ss"), // (4)
            s -> "now".equals(s)? new Date(): null // (5)
        );

        System.out.println("------");

        System.out.println(res);
    }

    // 日付文字列のパース
    @SafeVarargs
    public static Date parseDate(String date, Function<String, Date>... funcList) {
        for (Function<String, Date> func : funcList) {
            try {
                Date res = func.apply(date);

                if (res != null) {
                    return res;
                }
            } catch (Exception ex) {
                System.out.println("* " + ex.getMessage());
            }
        }
        return null;
    }
}
実行結果1 - (1) で成功(オフセット指定無しのため JST では +9時間される)
> java DateParse "2014-08-25T13:20:00"
------
Mon Aug 25 22:20:00 JST 2014
実行結果2 - (2) で成功
> java DateParse "2014-08-25T13:20:00+09:00"
* Text '2014-08-25T13:20:00+09:00' could not be parsed, unparsed text found at index 19
------
Mon Aug 25 13:20:00 JST 2014
実行結果3 - (3) で成功
> java DateParse "2014-08-25T13:20:00+09:00[Asia/Tokyo]"
* Text '2014-08-25T13:20:00+09:00[Asia/Tokyo]' could not be parsed, unparsed text found at index 19
* Text '2014-08-25T13:20:00+09:00[Asia/Tokyo]' could not be parsed, unparsed text found at index 25
------
Mon Aug 25 13:20:00 JST 2014
実行結果4 - (4) で成功
> java DateParse "2014-08-25 13:20:00"
* Text '2014-08-25 13:20:00' could not be parsed at index 10
* Text '2014-08-25 13:20:00' could not be parsed at index 10
* Text '2014-08-25 13:20:00' could not be parsed at index 10
------
Mon Aug 25 13:20:00 JST 2014
実行結果5 - (5) で成功
> java DateParse "now"
* Text 'now' could not be parsed at index 0
* Text 'now' could not be parsed at index 0
* Text 'now' could not be parsed at index 0
* java.text.ParseException: Unparseable date: "now"
------
Mon Aug 25 11:10:42 JST 2014
実行結果6 - 全失敗
> java DateParse "2014-08-25"
* Text '2014-08-25' could not be parsed at index 10
* Text '2014-08-25' could not be parsed at index 10
* Text '2014-08-25' could not be parsed at index 10
* java.text.ParseException: Unparseable date: "2014-08-25"
------
null

Either モナドを使った場合

同様の処理を FunctionalJava の Either を使って実装します。

Left にパース前の値(String)、Right にパース後の値(Date)を格納できるように Either の型を Either<String, Date> とします。

今回は Left の値 (String) に対して順次パース処理を試すようにしたいので、Either.left() で取得した Either.LeftProjection オブジェクトへパース処理を bind しています。

bind の引数には 「普通の値を取って Either を返す」 処理 (下記では F<String, Either<String, Date>>) を与える必要があるので、下記では eitherK というメソッドを定義し、通常のパース処理 (文字列を取って Date を返す F<String, Date>) を変換してから bind するようにしています。

なお、java.util.function.Function は使わず、全面的に FunctionalJava の fj.F を使うようにしました。

EitherDateParse.java
import fj.F;
import fj.data.Either;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneOffset;
import java.util.Date;

public class EitherDateParse {
    public static void main(String... args) {
        F<String, F<String, Date>> simpleDate = df -> s -> {
            try {
                return new SimpleDateFormat(df).parse(s);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };

        Either<String, Date> res = parseDate(
            Either.left(args[0]),
            s -> Date.from(LocalDateTime.parse(s).toInstant(ZoneOffset.UTC)), // (1)
            s -> Date.from(OffsetDateTime.parse(s).toInstant()), // (2)
            s -> Date.from(ZonedDateTime.parse(s).toInstant()), // (3)
            simpleDate.f("yyyy-MM-dd HH:mm:ss"), // (4)
            s -> "now".equals(s)? new Date(): null // (5)
        );

        System.out.println("------");

        System.out.println(res);
    }


    @SafeVarargs
    public static Either<String, Date> parseDate(Either<String, Date> date, F<String, Date>... funcList) {
        for (F<String, Date> func : funcList) {
            date = date.left().bind( eitherK(func) );
        }
        return date;
    }

    // F<S, T> の処理が例外発生か null であれば Left、そうでなければ Right を返す処理へ変換
    private static <S, T> F<S, Either<S, T>> eitherK(final F<S, T> func) {
        return s -> {
            try {
                T res = func.f(s);
                return (res == null)? Either.left(s): Either.right(res);
            } catch (Exception ex) {
                System.out.println("* " + ex.getMessage());
                return Either.left(s);
            }
        };
    }
}

実行結果は、Left や Right に包まれている部分を除けば Either を使わなかった場合と基本的に同じです。 (全失敗の場合は異なります)

実行結果1 - (1) で成功(オフセット指定無しのため JST では +9時間される)
> java -cp .;functionaljava-4.2-beta-1.jar EitherDateParse "2014-08-25T13:20:00"
------
Right(Mon Aug 25 22:20:00 JST 2014)
実行結果2 - (2) で成功
> java -cp .;functionaljava-4.2-beta-1.jar EitherDateParse "2014-08-25T13:20:00+09:00"
* Text '2014-08-25T13:20:00+09:00' could not be parsed, unparsed text found at index 19
------
Right(Mon Aug 25 13:20:00 JST 2014)
実行結果3 - (3) で成功
> java -cp .;functionaljava-4.2-beta-1.jar EitherDateParse "2014-08-25T13:20:00+09:00[Asia/Tokyo]"
* Text '2014-08-25T13:20:00+09:00[Asia/Tokyo]' could not be parsed, unparsed text found at index 19
* Text '2014-08-25T13:20:00+09:00[Asia/Tokyo]' could not be parsed, unparsed text found at index 25
------
Right(Mon Aug 25 13:20:00 JST 2014)
実行結果4 - (4) で成功
> java -cp .;functionaljava-4.2-beta-1.jar EitherDateParse "2014-08-25 13:20:00"
* Text '2014-08-25 13:20:00' could not be parsed at index 10
* Text '2014-08-25 13:20:00' could not be parsed at index 10
* Text '2014-08-25 13:20:00' could not be parsed at index 10
------
Right(Mon Aug 25 13:20:00 JST 2014)
実行結果5 - (5) で成功
> java -cp .;functionaljava-4.2-beta-1.jar EitherDateParse "now"
* Text 'now' could not be parsed at index 0
* Text 'now' could not be parsed at index 0
* Text 'now' could not be parsed at index 0
* java.text.ParseException: Unparseable date: "now"
------
Right(Mon Aug 25 11:59:01 JST 2014)
実行結果6 - 全失敗
> java -cp .;functionaljava-4.2-beta-1.jar EitherDateParse "2014-08-25"
* Text '2014-08-25' could not be parsed at index 10
* Text '2014-08-25' could not be parsed at index 10
* Text '2014-08-25' could not be parsed at index 10
* java.text.ParseException: Unparseable date: "2014-08-25"
------
Left(2014-08-25)

Either モナドを使った場合2 - 機能追加

次に下記のような機能を追加してみました。

  • (a) パース全失敗の場合は RuntimeException を throw する
  • (b) パース成功の場合は、その次の日の 0時 0分 0秒 の Date オブジェクトと共に 2要素の vectorV2) へ格納する

Either モナドを使えば if 文などでいちいち条件判定しなくても変換処理等を合成できるのが利点だと思います。

今回は、Either の結果出力に System.out.println() を直接使わず fj.Show.println() を使うようにしてみました。

EitherDateParse2.java
・・・
import fj.data.vector.V;
import fj.data.vector.V2;
import fj.Show;

import org.apache.commons.lang3.time.DateUtils;
・・・
public class EitherDateParse2 {
    public static void main(String... args) {
        ・・・
        Either<String, Date> res = parseDate(
            ・・・
        );

        // (a) パースが全失敗の場合は RuntimeException を throw
        res.left().bind( s -> {
            throw new RuntimeException("failed parse");
        });

        // (b) パース成功の場合は、次の日の 0時0分0秒 の結果と共に V2 へ格納して返す
        Either<String, V2<Date>> res2 = res.right().bind( d ->
            Either.right(
                V.v(d, DateUtils.truncate(DateUtils.addDays(d, 1), Calendar.DATE))
            )
        );

        System.out.println("------");

        Show.<String, V2<Date>>eitherShow(Show.anyShow(), Show.v2Show(Show.anyShow())).println(res2);
    }
    ・・・
}
実行結果 - 成功した場合
> java -cp .;functionaljava-4.2-beta-1.jar;commons-lang3-3.3.2.jar EitherDateParse2 "2014-08-25 13:20:00"
* Text '2014-08-25 13:20:00' could not be parsed at index 10
* Text '2014-08-25 13:20:00' could not be parsed at index 10
* Text '2014-08-25 13:20:00' could not be parsed at index 10
------
Right(<Mon Aug 25 13:20:00 JST 2014,Tue Aug 26 00:00:00 JST 2014>)
実行結果 - 全失敗した場合
> java -cp .;functionaljava-4.2-beta-1.jar;commons-lang3-3.3.2.jar EitherDateParse2 "2014-08-25"
* Text '2014-08-25' could not be parsed at index 10
* Text '2014-08-25' could not be parsed at index 10
* Text '2014-08-25' could not be parsed at index 10
* java.text.ParseException: Unparseable date: "2014-08-25"
Exception in thread "main" java.lang.RuntimeException: failed parse
        at EitherDateParse2.lambda$main$19(EitherDateParse2.java:37)
        ・・・

Java用 SSH クライアントライブラリ - ganymed-ssh2, sshj, JSch, Apache SSHD

主要な Java 用の SSH クライアントライブラリを使って簡単なサンプルを作成してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140814/

はじめに

Vagrant で実行中のゲスト OS に対して SSHls -al を実行し、その結果を出力するだけの単純な処理を Groovy で実装してみる事にします。

今回の実装のポイントとなる点は下記のようなところだと思います。

  • (1) Vagrant秘密鍵ファイル insecure_private_key で認証
  • (2) 未知のホスト鍵をチェックしないようにする (ssh における StrictHostKeyChecking=no と同等の処理)

Vagrant秘密鍵ファイル insecure_private_keyスクリプトファイルと同じディレクトリへコピーしておく事とします。

また、本件のサンプルコードではエラー処理や文字コード等を配慮していない点にご注意ください。

Ganymed SSH-2

元のオリジナルはかなり以前にメンテナンスが停止されていたり、Orion SSH や Trilead SSH のような同系統のライブラリが他にあったりと、どれを使えばよいのか分かり難い印象がありますが、とりあえず https://code.google.com/p/ganymed-ssh-2/ でメンテナンス継続されているものを使えば良さそうです。

ホスト鍵のチェックは connect メソッドで制御できるようです。(チェックしたければ ServerHostKeyVerifier を渡す)

ganymed_sample.groovy
@Grab('ch.ethz.ganymed:ganymed-ssh2:262')
import ch.ethz.ssh2.Connection

def con = new Connection('127.0.0.1', 2222)
con.connect()

// (1) Vagrant の秘密鍵ファイル insecure_private_key で認証
if (con.authenticateWithPublicKey('vagrant', new File('insecure_private_key'), null)) {
    def session = con.openSession()

    session.execCommand('ls -al')

    println session.stdout.text

    session.close()
}

con.close()
実行結果
> groovy ganymed_sample.groovy

total 24
drwx------. 3 vagrant vagrant 4096 Aug  1 09:05 .
drwxr-xr-x. 3 root    root      20 Aug  1 08:36 ..
-rw-------. 1 vagrant vagrant  202 Aug 10 05:28 .bash_history
-rw-r--r--. 1 vagrant vagrant   18 Jun 10 00:31 .bash_logout
-rw-r--r--. 1 vagrant vagrant  193 Jun 10 00:31 .bash_profile
-rw-r--r--. 1 vagrant vagrant  231 Jun 10 00:31 .bashrc
drwx------. 2 vagrant vagrant   28 Aug  1 09:05 .ssh
-rw-------. 1 vagrant vagrant  644 Aug  1 09:05 .viminfo

sshj - SSHv2 library for Java

authPublickeyconnect の後に実行する必要があります。

また、未知のホスト鍵をチェックしないようにするには、addHostKeyVerifierPromiscuousVerifier を設定します。 (デフォルトではチェックするようになっています)

sshj_sample.groovy
@Grab('net.schmizz:sshj:0.10.0')
@Grab('org.slf4j:slf4j-nop:1.7.7')
import net.schmizz.sshj.SSHClient
import net.schmizz.sshj.common.IOUtils
import net.schmizz.sshj.transport.verification.PromiscuousVerifier

def client = new SSHClient()
// (2) 未知のホスト鍵をチェックしない
client.addHostKeyVerifier(new PromiscuousVerifier())

client.connect('127.0.0.1', 2222)
// (1) Vagrant の秘密鍵ファイル insecure_private_key で認証
client.authPublickey('vagrant', client.loadKeys('insecure_private_key'))

def session = client.startSession()

def cmd = session.exec('ls -al')

println IOUtils.readFully(cmd.inputStream)

session.close()
client.disconnect()
実行結果
> groovy sshj_sample.groovy

total 24
drwx------. 3 vagrant vagrant 4096 Aug  1 09:05 .
drwxr-xr-x. 3 root    root      20 Aug  1 08:36 ..
-rw-------. 1 vagrant vagrant  202 Aug 10 05:28 .bash_history
-rw-r--r--. 1 vagrant vagrant   18 Jun 10 00:31 .bash_logout
-rw-r--r--. 1 vagrant vagrant  193 Jun 10 00:31 .bash_profile
-rw-r--r--. 1 vagrant vagrant  231 Jun 10 00:31 .bashrc
drwx------. 2 vagrant vagrant   28 Aug  1 09:05 .ssh
-rw-------. 1 vagrant vagrant  644 Aug  1 09:05 .viminfo

JSch - Java Secure Channel

未知のホスト鍵をチェックしないようにするには setConfigStrictHostKeyCheckingno にします。

jsch_sample.groovy
@Grab('com.jcraft:jsch:0.1.51')
import com.jcraft.jsch.JSch

def jsch = new JSch()

// (1) Vagrant の秘密鍵ファイル insecure_private_key で認証
jsch.addIdentity('insecure_private_key')
// (2) 未知のホスト鍵をチェックしない
jsch.setConfig('StrictHostKeyChecking', 'no')

def session = jsch.getSession('vagrant', '127.0.0.1', 2222)

session.connect()

def ch = session.openChannel('exec')
ch.setCommand('ls -al')

ch.connect()

println ch.inputStream.text

ch.disconnect()
session.disconnect()
実行結果
> groovy jsch_sample.groovy

total 24
drwx------. 3 vagrant vagrant 4096 Aug  1 09:05 .
drwxr-xr-x. 3 root    root      20 Aug  1 08:36 ..
-rw-------. 1 vagrant vagrant  202 Aug 10 05:28 .bash_history
-rw-r--r--. 1 vagrant vagrant   18 Jun 10 00:31 .bash_logout
-rw-r--r--. 1 vagrant vagrant  193 Jun 10 00:31 .bash_profile
-rw-r--r--. 1 vagrant vagrant  231 Jun 10 00:31 .bashrc
drwx------. 2 vagrant vagrant   28 Aug  1 09:05 .ssh
-rw-------. 1 vagrant vagrant  644 Aug  1 09:05 .viminfo

Apache SSHD

Apache MINA をベースに SSH サーバーとクライアントの両方の API を備えたライブラリです。 他のライブラリと比べると多少面倒なように思います。

close メソッドに false を指定すると graceful にクローズします。

sshd_sample.groovy
@Grab('org.apache.sshd:sshd-core:0.12.0')
@Grab('org.apache.mina:mina-core:3.0.0-M2')
@Grab('org.bouncycastle:bcpkix-jdk15on:1.51')
@Grab('org.slf4j:slf4j-nop:1.7.7')
import org.apache.sshd.ClientChannel
import org.apache.sshd.SshClient
import org.apache.sshd.common.keyprovider.FileKeyPairProvider

def client = SshClient.setUpDefaultClient()
client.start()

def session = client.connect('vagrant', '127.0.0.1', 2222).await().getSession()

// (1) Vagrant の秘密鍵ファイル insecure_private_key で認証
def keyProvider = new FileKeyPairProvider(['insecure_private_key'] as String[])
session.addPublicKeyIdentity(keyProvider.loadKeys().iterator().next())

def auth = session.auth()
// verify の実行が必要
auth.verify()

if (auth.isSuccess()) {
    def ch = session.createExecChannel('ls -al')
    def baos = new ByteArrayOutputStream()

    ch.setOut(baos)

    ch.open()
    // コマンド完了まで待機
    ch.waitFor(ClientChannel.CLOSED, 0)
    ch.close(false)

    println baos.toString()
}

session.close(false)
client.stop()
実行結果
> groovy sshd_sample.groovy

total 24
drwx------. 3 vagrant vagrant 4096 Aug  1 09:05 .
drwxr-xr-x. 3 root    root      20 Aug  1 08:36 ..
-rw-------. 1 vagrant vagrant  202 Aug 10 05:28 .bash_history
-rw-r--r--. 1 vagrant vagrant   18 Jun 10 00:31 .bash_logout
-rw-r--r--. 1 vagrant vagrant  193 Jun 10 00:31 .bash_profile
-rw-r--r--. 1 vagrant vagrant  231 Jun 10 00:31 .bashrc
drwx------. 2 vagrant vagrant   28 Aug  1 09:05 .ssh
-rw-------. 1 vagrant vagrant  644 Aug  1 09:05 .viminfo

Arrow (Kleisli) で List モナド - Haskell, Frege, Scalaz

Scalaz でリストモナド - Kleisli による関数合成 」等で試してきた List モナドを使ったチェスのナイト移動の処理を Arrow (Kleisli) を使って実装し直してみました。

Arrow は計算のための汎用的なインターフェースで、モナドを扱うための Arrow として Kleisli があります。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140810/

Haskell の場合

Haskell の Arrow は >>><<< で合成できるようになっています。

Kleisli はモナドを扱うための Arrow なので、下記では List モナドを返す関数 moveKnight を Kleisli へ包んで合成しています。

Kleisli から包んだ関数を取り出すには runKleisli を使います。

3手版

まずは 3手版です。

以前の List モナド版との違いは in3canReachIn3 関数を Arrow で実装し直した点です。

Kleisli を使えば、モナド値が無くてもモナドを返す関数 (通常の値を取ってモナドを返す関数) を簡単に合成できるので in3 はポイントフリースタイルで定義しました。 (このため canReachIn3 関数の引数の順序が 以前のもの と異なっています)

また、通常の関数は Arrow のインスタンスなので、canReachIn3 関数の部分は単純に canReachIn3 end = runKleisli in3 >>> elem end とする事も可能です。

move_knight.hs
import Control.Arrow

type KnightPos = (Int, Int)

moveKnight :: KnightPos -> [KnightPos]
moveKnight (c, r) = filter onBoard
    [
        (c + 2, r - 1), (c + 2, r + 1),
        (c - 2, r - 1), (c - 2, r + 1),
        (c + 1, r - 2), (c + 1, r + 2),
        (c - 1, r - 2), (c - 1, r + 2)
    ]
    where onBoard (c', r') = c' `elem` [1..8] && r' `elem` [1..8]

-- 3手先の移動位置を列挙
in3 :: Kleisli [] KnightPos KnightPos
in3 = Kleisli moveKnight >>> Kleisli moveKnight >>> Kleisli moveKnight

-- 指定位置に3手で到達できるか否かを判定
canReachIn3 :: Arrow a => KnightPos -> a KnightPos Bool
canReachIn3 end = arr (runKleisli in3) >>> arr (elem end)
-- 以下でも可
-- canReachIn3 :: KnightPos -> KnightPos -> Bool
-- canReachIn3 end = runKleisli in3 >>> elem end

main = do
    print $ runKleisli in3 $ (6, 2)

    print $ canReachIn3 (6, 1) $ (6, 2)
    print $ canReachIn3 (7, 3) $ (6, 2)
実行結果
> runghc move_knight.hs

[(8,1),(8,3),・・・
・・・
,(3,4),(3,8)]
True
False

N手版

3手版と同様に inManycanReachInMany 関数を Arrow で実装し直してみました。

move_knight_many.hs
・・・
-- N手先の移動位置を列挙
inMany :: Int -> Kleisli [] KnightPos KnightPos
inMany x = foldr (>>>) returnA (replicate x (Kleisli moveKnight))

-- 指定位置にN手で到達できるか否かを判定
canReachInMany :: Arrow a => Int -> KnightPos -> a KnightPos Bool
canReachInMany x end = arr (runKleisli (inMany x)) >>> arr (elem end)
-- 以下でも可
-- canReachInMany :: Int -> KnightPos -> KnightPos -> Bool
-- canReachInMany x end = runKleisli (inMany x) >>> elem end

main = do
    print $ runKleisli (inMany 3) $ (6, 2)

    print $ canReachInMany 3 (6, 1) $ (6, 2)
    print $ canReachInMany 3 (7, 3) $ (6, 2)
実行結果
> runghc move_knight_many.hs

[(8,1),(8,3),・・・
・・・
,(3,4),(3,8)]
True
False

Frege の場合

Frege は Haskell とほとんど同じ実装になりますが、下記の点が異なります。

  • >>> の代わりに . で Arrow を合成
  • runKleisli の代わりに run を使用

なお、.>>> と合成の向きが異なります。

3手版

3手版です。

move_knight.fr
package sample.MoveKnight where

import frege.control.Arrow
import frege.control.arrow.Kleisli

type KnightPos = (Int, Int)

moveKnight :: KnightPos -> [KnightPos]
moveKnight (c, r) = filter onBoard
    [
        (c + 2, r - 1), (c + 2, r + 1),
        (c - 2, r - 1), (c - 2, r + 1),
        (c + 1, r - 2), (c + 1, r + 2),
        (c - 1, r - 2), (c - 1, r + 2)
    ]
    where onBoard (c', r') = c' `elem` [1..8] && r' `elem` [1..8]

-- 3手先の移動位置を列挙
in3 :: Kleisli [] KnightPos KnightPos
in3 = Kleisli moveKnight . Kleisli moveKnight . Kleisli moveKnight

-- 指定位置に3手で到達できるか否かを判定
canReachIn3 :: Arrow a => KnightPos -> a KnightPos Bool
canReachIn3 end = arr (elem end) . arr in3.run
-- 以下でも可
-- canReachIn3 :: KnightPos -> KnightPos -> Bool
-- canReachIn3 end = elem end . in3.run

main args = do
    println $ in3.run $ (6, 2)

    println $ canReachIn3 (6, 1) $ (6, 2)
    println $ canReachIn3 (7, 3) $ (6, 2)
実行結果
> java -jar frege3.21.586-g026e8d7.jar move_knight.fr
・・・
> java -cp .;frege3.21.586-g026e8d7.jar sample.MoveKnight

[(8, 1), (8, 3), ・・・
・・・
 (3, 4), (3, 8)]
true
false

N手版

N手版です。

move_knight_many.fr
package sample.MoveKnightMany where

・・・
-- N手先の移動位置を列挙
inMany :: Int -> Kleisli [] KnightPos KnightPos
inMany x = foldr (.) id (replicate x (Kleisli moveKnight))

-- 指定位置にN手で到達できるか否かを判定
canReachInMany :: Arrow a => Int -> KnightPos -> a KnightPos Bool
canReachInMany x end = arr (elem end) . arr (inMany x).run
-- 以下でも可
-- canReachInMany :: Int -> KnightPos -> KnightPos -> Bool
-- canReachInMany x end = elem end . (inMany x).run

main args = do
    println $ (inMany 3).run $ (6, 2)

    println $ canReachInMany 3 (6, 1) $ (6, 2)
    println $ canReachInMany 3 (7, 3) $ (6, 2)
実行結果
> java -jar frege3.21.586-g026e8d7.jar move_knight_many.fr
・・・
> java -cp .;frege3.21.586-g026e8d7.jar sample.MoveKnightMany

[(8, 1), (8, 3), ・・・
・・・
 (3, 4), (3, 8)]
true
false

Scalaz の場合

最後に Scalaz を使った Scala による実装です。
Haskell と同様に >>> で Arrow を合成できるようになっています。

3手版

3手版です。

MoveKnight.scala
package sample

import scalaz._
import Scalaz._

object MoveKnight extends App {

    type KnightPos = Tuple2[Int, Int]

    val inRange = (p: Int) => 1 to 8 contains p

    val moveKnight = (p: KnightPos) => List(
        (p._1 + 2, p._2 - 1), (p._1 + 2, p._2 + 1),
        (p._1 - 2, p._2 - 1), (p._1 - 2, p._2 + 1),
        (p._1 + 1, p._2 - 2), (p._1 + 1, p._2 + 2),
        (p._1 - 1, p._2 - 2), (p._1 - 1, p._2 + 2)
    ).filter { case (x, y) => inRange(x) && inRange(y) }

    // 3手先の移動位置を列挙
    val in3 = Kleisli(moveKnight) >>> Kleisli(moveKnight) >>> Kleisli(moveKnight)
    // 以下でも可
    // val in3 = Kleisli(moveKnight) >==> moveKnight >==> moveKnight

    // 指定位置に3手で到達できるか否かを判定
    val canReachIn3 = (end: KnightPos) => in3.run >>> { xs => xs.contains(end) }

    in3 (6, 2) |> println

    (6, 2) |> canReachIn3 (6, 1) |> println
    (6, 2) |> canReachIn3 (7, 3) |> println
}
実行結果
> gradle run

MoveKnight
:compileJava UP-TO-DATE
:compileScala UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
List((8,1), (8,3), ・・・
・・・
・・・, (3,4), (3,8))
true
false

N手版

N手版です。

MoveKnightMany.scala
package sample

import scalaz._
import Scalaz._

object MoveKnightMany extends App {
    ・・・
    // N手先の移動位置を列挙
    val inMany = (x: Int) => List.fill(x) { Kleisli(moveKnight) }.reduce { (a, b) => a >>> b }
    // 以下でも可
    // val inMany = (x: Int) => List.fill(x) { Kleisli(moveKnight) }.reduce { (a, b) => a >=> b }

    // 指定位置にN手で到達できるか否かを判定
    val canReachInMany = (x: Int) => (end: KnightPos) => inMany(x).run >>> { xs => xs.contains(end) }

    (6, 2) |> inMany(3) |> println

    (6, 2) |> canReachInMany(3)(6, 1) |> println
    (6, 2) |> canReachInMany(3)(7, 3) |> println
}
実行結果
> gradle run -Pmany

MoveKnightMany
:compileJava UP-TO-DATE
:compileScala UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
List((8,1), (8,3), ・・・
・・・
・・・, (3,4), (3,8))
true
false

なお、ビルドと実行には下記のような Gradle ビルド定義ファイルを使用しました。

build.gradle
apply plugin: 'application'
apply plugin: 'scala'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.scala-lang:scala-library:2.11.2'
    compile 'org.scalaz:scalaz-core_2.11:7.1.0'
}

if (!hasProperty('many')) {
    println 'MoveKnight'
    mainClassName = 'sample.MoveKnight'
}
else {
    println 'MoveKnightMany'
    mainClassName = 'sample.MoveKnightMany'
}

Roy で List モナド

前回に続き、今回も Roy を試してみます。

Roy ではモナドを使った do 記法を使えるようなので、JavaScript で List モナド - Monadic 等で試したチェスのナイト移動の List モナド処理を同じように実装してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140724/

モナドの定義

Roy では下記のように return と bind を実装してモナドを定義します。

let <モナド> {
    return: \x -> ・・・
    bind: \x f -> ・・・
}

これで Monadic と同じように do <モナド> ・・・ のような記法が使えます。

それでは、List モナドを定義して do を簡単に使ってみました。
Underscore.js に Haskell の concat と同等の処理が無さそうだったので concat を自前で定義しています。

sample.roy
let _ = require 'underscore'

let concat xs = _.reduce xs (\a b -> a.concat b) []
// リストモナド
let listMonad = {
    return: \x -> [x]
    bind: \xs f -> concat (_.map xs (\y -> f y))
}
// do 記法
let res = do listMonad
    x <- [1, 3, 5]
    y <- ['a', 'b']
    return (x * 2, y)

console.log res

do でタプル (要素1, 要素2, ・・・) を return していますが、下記のようにタプルは配列として扱われるようです。

実行結果
> roy -r sample.roy

[ [ 2, 'a' ],
  [ 2, 'b' ],
  [ 6, 'a' ],
  [ 6, 'b' ],
  [ 10, 'a' ],
  [ 10, 'b' ] ]

3手版

それでは本題に入ります。

まずは、List モナドを使って下記の処理を実装してみます。

  • 3手先のナイトの移動位置を全て列挙する処理
  • 3手後に指定の終了位置に到達するか否かを判定する処理

Haskell と同様に type で型シノニムを定義できるので、ナイトの位置情報を表す KnightPos という型シノニムを定義しました。

関数の引数では (<引数>: <型>) のように型を指定する事も可能です。

また、複数の引数は Haskell と同様に半角スペースで区切る事になります。

move_knight.roy (3手移動版)
let _ = require 'underscore'

let concat xs = _.reduce xs (\a b -> a.concat b) []

let listMonad = {
    return: \x -> [x]
    bind: \xs f -> concat (_.map xs (\y -> f y))
}
// 型シノニムの定義
type KnightPos = {c: Number, r: Number}

let inRange (n: Number) = _.contains (_.range 1 9) n

// ナイトの次の移動先を列挙
let moveKnight (p: KnightPos) = _.filter [
    {c: p.c + 2, r: p.r - 1}, {c: p.c + 2, r: p.r + 1},
    {c: p.c - 2, r: p.r - 1}, {c: p.c - 2, r: p.r + 1},
    {c: p.c + 1, r: p.r - 2}, {c: p.c + 1, r: p.r + 2},
    {c: p.c - 1, r: p.r - 2}, {c: p.c - 1, r: p.r + 2}
] ( \t -> (inRange t.c) && (inRange t.r) )

// 3手先の移動位置を列挙(重複あり)
let in3 (start: KnightPos) = do listMonad
    fst <- moveKnight start
    snd <- moveKnight fst
    moveKnight snd

console.log (in3 {c: 6, r: 2})

// 指定位置に3手で到達できるか否かを判定
let canReachIn3 (start: KnightPos) (end: KnightPos) = _.any (in3 start) (\p -> p.c == end.c && p.r == end.r)

console.log (canReachIn3 {c: 6, r: 2} {c: 6, r: 1})
console.log (canReachIn3 {c: 6, r: 2} {c: 7, r: 3})
実行結果
> roy -r move_knight.roy

[ { c: 8, r: 1 },
  { c: 8, r: 3 },
  { c: 4, r: 1 },
  { c: 4, r: 3 },
  ・・・
  { c: 3, r: 4 },
  { c: 3, r: 8 } ]
true
false

N手版

先程は 3手で固定していましたが、任意の手数を指定できるようにしてみます。

こちらは do を使わず、直接 listMonad.bind を使って n 回のナイト移動処理を連結するようにしました。

move_knight_many.roy (N手移動版)
・・・
// n手先の移動位置を列挙(重複あり)
let inMany (n: Number) (start: KnightPos) = _.reduce (_.range n) (\a b -> listMonad.bind a moveKnight) [start]

console.log (inMany 3 {c: 6, r: 2})

// 指定位置に n手で到達できるか否かを判定
let canReachInMany (n: Number) (start: KnightPos) (end: KnightPos) = _.any (inMany 3 start) (\p -> p.c == end.c && p.r == end.r)

console.log (canReachInMany 3 {c: 6, r: 2} {c: 6, r: 1})
console.log (canReachInMany 3 {c: 6, r: 2} {c: 7, r: 3})
実行結果
> roy -r move_knight_many.roy

[ { c: 8, r: 1 },
  { c: 8, r: 3 },
  { c: 4, r: 1 },
  { c: 4, r: 3 },
  ・・・
  { c: 3, r: 4 },
  { c: 3, r: 8 } ]
true
false

Roy による関数合成

Groovy, Scala, F#, Haskell による関数・クロージャの合成Java SE 8 で関数合成 で実施したものと同様の関数合成を Roy で試してみました。

Roy は JavaScriptコンパイルできる関数型のプログラミング言語です。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140721/

はじめに

Node.js で Roy を実行するには roy を npm install するだけです。

Roy のインストール例
> npm install roy

roy@0.2.2 node_modules\roy
├── unicode-categories@0.9.1
├── underscore@1.2.0
├── source-map@0.1.8 (amdefine@0.1.0)
└── escodegen@0.0.22 (estraverse@0.0.4, esprima@1.0.4)

これでカレントディレクトリの node_modules へ roy がインストールされました。

Underscore.js を使った関数合成

Roy で関数合成を行うには Underscore.js の compose を使えばよいようです。 (roy/lib/prelude.roy に compose 関数が定義されているもののコメントアウトされているため)

sample.roy
let _ = require 'underscore'

let plus n = n + 3
let times n = n * 2

let f1 = _.compose times plus
let f2 = _.compose plus times

// times(plus(4)) = 14
console.log(f1 4)
// plus(times(4)) = 11
console.log(f2 4)

Roy のスクリプトファイルを直接実行するには -r オプションを指定して実行します。

実行結果
> roy -r sample.roy

14
11

オプションを指定せずに実行すると Roy スクリプトJavaScriptコンパイルし、.js ファイルが生成されます。

コンパイル実行例
> roy sample.roy
sample.js (生成されたファイル)
var _ = require('underscore');
var plus = function (n) {
    return n + 3;
};
var times = function (n) {
    return n * 2;
};
var f1 = _.compose(times, plus);
var f2 = _.compose(plus, times);
// times(plus(4)) = 14
console.log(f1(4));
// plus(times(4)) = 11
console.log(f2(4));//@ sourceMappingURL=sample.js.map

自前で compose 関数を定義して関数合成

Underscore.js の代わりに compose 関数を自前で定義するのも簡単です。

sample2.roy
let plus n = n + 3
let times n = n * 2

let compose f g = \x -> f (g x)

let f1 = compose times plus
let f2 = compose plus times

// times(plus(4)) = 14
console.log(f1 4)
// plus(times(4)) = 11
console.log(f2 4)
実行結果
> roy -r sample2.roy

14
11

Apache Solr を組み込み実行

オープンソース全文検索エンジン Apache Solr は、 Servlet として実装されており、通常は jetty 等のサーブレットエンジン(コンテナ)で実行しますが、今回は組み込み実行を試してみました。

ソースは http://github.com/fits/try_samples/tree/master/blog/20140710/

はじめに

Solr を組み込み実行 (サーバーを起動せずに直接処理を実行) するには下記のような方法が考えられます。

  • (1) EmbeddedSolrServer を使用
  • (2) SolrCore を使用

(1) は SolrJ の API を使うので高レベル API、(2) は Core API をそのまま使うので低レベル API といったところでしょうか。

(1) EmbeddedSolrServer を使用

EmbeddedSolrServer を使って検索処理を実装するのは簡単です。

今回は、実行時引数 (第1引数で Solr のホームディレクトリ、第2引数でコア名) で指定したコアのドキュメントを全件取得するようにしてみました。

search1.groovy
@Grab('org.apache.solr:solr-core:4.9.0')
@Grab('org.slf4j:slf4j-nop:1.7.7')
import org.apache.solr.core.CoreContainer
import org.apache.solr.client.solrj.SolrQuery
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer

def cores = new CoreContainer(args[0])
cores.load()

def server = new EmbeddedSolrServer(cores, args[1])
// 全件取得のクエリ
def q = new SolrQuery(query: '*:*')
// 検索
def result = server.query(q)
// 結果の出力
result.results.each {
    println it
}

server.shutdown()

なお、EmbeddedSolrServer をインスタンス化している箇所を new HttpSolrServer('http://localhost:8983/solr/collection1') のように書き換えれば、サーブレットエンジン上で実行している Solr サーバーに対して検索を行うようになります。

実行結果は下記の通りです。

実行結果
> groovy search1.groovy /solr-4.9.0/example/solr collection1

[id:GB18030TEST, name:Test with some GB18030 encoded characters, features:[No accents here, ?是一个功能, This is a feature (translated), ??文件是很有光?, This document is very shiny (translated)], price:0.0, price_c:0,USD, inStock:true, _version_:1473119790218346496]
・・・

(2) SolrCore を使用

次に、同様の処理を SolrCore を使って実装してみました。

SolrCore では XXXResponseWriter (BinaryResponseWriterJSONResponseWriter 等) を使って処理結果をストリームへ出力します。

search2.groovy
@Grab('org.apache.solr:solr-core:4.9.0')
@Grab('org.slf4j:slf4j-nop:1.7.7')
import org.apache.solr.core.CoreContainer
import org.apache.solr.request.LocalSolrQueryRequest
import org.apache.solr.response.SolrQueryResponse
import org.apache.solr.response.BinaryResponseWriter
import org.apache.solr.common.util.JavaBinCodec

def cores = new CoreContainer(args[0])
cores.load()

def core = cores.getCore(args[1])
// 検索ハンドラ取得
def handler = core.getRequestHandler('/query')

def req = new LocalSolrQueryRequest(core, [
    q: ['*:*'] as String[] // 全件取得
])
def res = new SolrQueryResponse()
// 検索
core.execute(handler, req, res)

def writer = new ByteArrayOutputStream()

def rw = new BinaryResponseWriter()
// 検索結果をストリームへ出力
rw.write(writer, req, res)

def resolver = new BinaryResponseWriter.Resolver(req, res.getReturnFields())
def bais = new ByteArrayInputStream(writer.toByteArray())
// バイト配列を Java オブジェクト化 (ここでは SimpleOrderedMap オブジェクトが返ります)
def result = new JavaBinCodec(resolver).unmarshal(bais)
// 結果の出力
result.response.each {
    println it
}

core.close()
cores.shutdown()

Solr に含まれている example/solrcollection1 コアを使う場合は、/query の他に /select ハンドラも利用でき、違いはデフォルト設定値だけのようです。 (/query は indent が true に設定されている等)

実行結果は下記の通りです。

実行結果
> groovy search2.groovy /solr-4.9.0/example/solr collection1

[id:GB18030TEST, name:Test with some GB18030 encoded characters, features:[No accents here, ?是一个功能, This is a feature (translated), ??文件是很有光?, This document is very shiny (translated)], price:0.0, price_c:0,USD, inStock:true, _version_:1473119790218346496]
・・・

JSON で結果出力

JSON で検索結果を出力したい場合、BinaryResponseWriter の代わりに JSONResponseWriter を使用します。

search2_json.groovy
・・・
// 検索
core.execute(handler, req, res)

def writer = new StringWriter()

def rw = new JSONResponseWriter()
rw.write(writer, req, res)
// 結果の出力 (JSON)
println writer.toString()
・・・
実行結果
> groovy search2_json.groovy /solr-4.9.0/example/solr collection1

{
  "responseHeader":{
    "status":0,
    "QTime":94,
    "params":{
      "q":"*:*"}},
  "response":{"numFound":32,"start":0,"docs":[
      {
        "id":"GB18030TEST",
        "name":"Test with some GB18030 encoded characters",
        "features":["No accents here",
          "?是一个功能",
          "This is a feature (translated)",
          "??文件是很有光?",
          "This document is very shiny (translated)"],
        "price":0.0,
        "price_c":"0,USD",
        "inStock":true,
        "_version_":1473119790218346496},
  ・・・