唯物是真 @Scaled_Wurm

プログラミング(主にPython2.7)とか機械学習とか

JavaのJShellワンライナーでスクリーンショットを撮る(実用性はない)

以前JShellでワンライナーのスクリプトっぽく書くのに挑戦しました
sucrose.hatenablog.com
この記事を書いてからJavaでワンライナーを書いて他の言語よりも嬉しい場合ってあんまりないかもと思い始めてきました

とりあえず思いついた例として、JavaのRobotクラスはスクリーンショットを取ったりキー入力やマウスの入力ができて、これは他の言語では標準ではあまり入ってなくて便利かもと一瞬考えました
というわけでスクリーンショットを撮るワンライナーを書いてみました

前の記事には書きませんでしたがjshellJAVASEという引数を渡すとImports all Java SE packages.してくれるらしいのでこれを使います。ちなみに起動がすごく遅くなります

コード

変数に代入したりせず簡潔に(?)書けました
screen.pngというファイル名で保存します
実行に時間がかかるのでいつスクリーンショットが取られるのかは動かしてみないとわかりません

echo 'ImageIO.write(new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize())), "PNG", new File("screen.png"))' | jshell JAVASE -

JavaのJShellでワンライナーのスクリプトを書く方法(ついでにFizzBuzzのコードゴルフ)

Java9からJShellというREPL(対話型の実行環境)が使えるようになりました
main関数などを書かずともコード片を入力すればその実行結果を試すことができます

対話形式以外にもファイルもしくは標準入力でコードを渡すと実行してくれるので、簡単なワンライナーの実行に使ってみようというのがこの記事の内容です

f:id:sucrose:20180505155409p:plain

ワンライナーの実行方法

2つの実行方法があります
1つは標準入力経由でコードを渡す方法、もう1つはファイルとしてコードを渡す方法です

以下に出てくるコマンドではjshellへのパスが通っているものとします

標準入力経由でコードを実行する

標準入力からコードを実行するときは、jshellの引数に-を渡すとよいです
渡さなくてもコード自体は実行されますが対話的モードっぽい出力も表示されてしまいます

echo 'System.out.println("Hello, World!")' | jshell -

ファイル経由でコードを実行する

jshellの引数には複数のファイルを渡すことができ、渡した順番に実行されます
このときそれぞれのファイル間の処理は独立ではなく、1つ目のファイルで定義した変数に2つ目のファイルのコードでアクセスしたりできます

ファイルを作るのはめんどくさいので代わりにBashのプロセス置換(コマンドの実行結果をファイルとして使える)を使ってコードを渡すと以下のような感じになります
対話的なモードで実行されてしまうので、ワンライナーとしては最後に対話モードの終了を表す/exit/exを付ける必要があります

jshell <(echo 'System.out.println("Hello, World!");/ex')
jshell <(echo 'System.out.println("Hello, World!");') <(echo '/ex')

PRINTING

jshellの引数にPRINTINGを渡すとprintprintlnprintfがクラス名などがなしでそのまま呼べるようになります

echo 'println("Hello, World!")' | jshell PRINTING -
jshell PRINTING <(echo 'println("Hello, World!");/ex')

オプションなどを渡す

標準入力や引数のファイルはそれぞれコードとして解釈されてしまうので、動作が変わるオプション的なものを渡したいときには環境変数やシステムプロパティとして渡したり、無理矢理Javaのコードに変換して動かす必要があります

環境変数

System.getenv().get(環境変数の名前)で値を取ってきてがんばる

プロパティ

システムプロパティにてきとうに値を入れて参照する

echo 'println(System.getProperty("foo"))' | jshell -R-Dfoo=123 PRINTING -

シェルの``によるコマンドの結果の展開を利用すれば標準入力を無理矢理受け取るのにも使えます(長さ制限があるので実用的ではないかもしれませんが)

seq 1 10 | jshell -R-Dfoo="`cat`" PRINTING <(echo 'println(System.getProperty("foo"));/ex')

コードとして渡す

最初に変数を定義するようなコード片をがんばって作って、その値を後続の処理で参照する

jshell PRINTING <(echo 'int a = 1234') <(echo 'println(a);/ex')

実践編

とりあえずFizzBuzzのワンライナーを書いてついでにコードゴルフ(ショートコーディング)をしてみました
classやmain関数などを書く必要がないのでスッキリします

ループ版

echo 'int i;for(;++i<101;)println((i%3<1?"Fizz":"")+(i%5<1?"Buzz":i%3<1?"":i))'|jshell PRINTING -

Stream版

echo 'IntStream.range(1,101).mapToObj(i->(i%3<1?"Fizz":"")+(i%5<1?"Buzz":i%3<1?"":i)).forEach(System.out::println)'|jshell -

まとめ

JShellを使えばワンライナーもある程度は書けそう
標準入力はコードとして解釈されてしまうので、パイプで繋いでテキストを処理したりするのにはそこまで向いていなさそうなのが残念