はじめに
自宅のプロジェクトの1つで、小さな外部プロセスマネージャーを作成する必要がありました。 アプリケーションは、外部デーモンを起動し、定期的にその状態を監視し、オフ、オン、設定の変更などが必要な場合に使用できる必要があります。 そのようなタスクのためのJavaの既存の機能は非常に少なく、Scalaを同時に扱っていたので、Scalaがどのようにそれを使っているかを見ることにしました。 そして、驚いたことに、Scalaは外部プロセスを操作するための優れたAPIを私の意見で提供しています。
この記事では、これについて詳しく説明したいと思います。
プロセス開始
2つの特性は、プロセスを使用した作業の基礎です 。これらはscala.sys.process.Processとscala.sys.process.ProcessBuilderです。
プロセスを使用すると、すでに実行中のプロセスを操作でき、ProcessBuilderを使用すると、起動パラメーターを構成できます。
これらのエンティティはscala.sys.processパッケージに含まれています。 簡単な例を実行するには、コードを実行します:
scala> import scala.sys.process._ scala> val process: Process = Process("echo Hello World").run() scala> println(process.exitValue())
runメソッドは、宣言がProcessBuilder トレイにあるプロセスを開始するためのメインメソッドです。 タイプProcessのオブジェクトへの参照を返します。 実行中のプロセスはバックグラウンドで実行され、データはコンソールに出力されます。 プロセストレイで宣言された2つのメソッドがあります。
-
exitValue()
-プロセスの完了を待機し、終了コードを返します。 -
destroy()
-実行中のプロセスを破棄します。
この特性は、標準のJavaクラスjava.lang.Processに非常に似ています。
ProcessBuilder トレイでプロセスを開始するための特別な方法があります 。 主なものについて簡単に説明します。
-
!
-プロセスを開始し、完了を待機し、データをコンソールに実行し、結果としてプロセス完了コードを返します。 -
!!
-プロセスを開始し、完了を待機し、完了コードがゼロと異なる場合、コンソールにデータを表示します-例外をスローし、その結果、プロセスの出力を文字列として返します。 -
lines
-プロセスを開始し、 ストリーム [文字列]を返します。 このスレッドを使用すると、プロセスと並行してプロセスデータを読み取ることができます。 情報が利用できない場合、Streamはブロックされ、情報が再表示されるか、プロセスが実行を完了するまで待機します。 プロセス終了コードがゼロ以外の場合、メソッドは例外をスローします。 例外を回避するには、lines_!;を呼び出す必要があります。 -
run
プロセスを開始し、プロセスへのリンクを返します。
私のプロジェクトでは、外部プロセスへのリンクを保存する必要がなかったため、runメソッドをほとんど使用しませんでした。 そして、ここに方法があり
!
私にぴったり。
前の例を次のように書き換えることができます。
scala> Process("echo Hello World!").! Hello World! res1: Int = 0 scala> Process("echo Hello World!").!! res2: String = "Hello World!" scala> Process("echo Hello World!").lines res3: Stream[String] = Stream(Hello World!, ?)
暗黙的なキャスト
文字列( java.lang.String )とシーケンス( scala.collection.Seq )をProcessBuilder トレイトに暗黙的にキャストするメソッドがあります 。
次のようにコードを記述できます。
scala> "echo Hello World!".! Hello World! res2: Int = 0
または:
scala> Seq("echo", "Hello", "World!").! Hello World! res3: Int = 0
これにより、大幅に記述が削減および簡素化され、コードがより理解しやすくなります。
これにより、将来のエラーの数が減少します。
プロセスの組み合わせ(パイプ)
プロセス呼び出しは、Linuxコマンドチェーンに似たチェーンに結合できます。
scala> "ls".! 11.txt 1.txt 2.txt 3.txt res2: Int = 0 scala> ("ls" #| "grep 1").! 11.txt 1.txt res6: Int = 0
lsコマンドからの出力は、grep入力に向けられました。 Grepは、エントリ1で受信した情報を除外しました。
次のような条件付き操作を実行できます。
scala> ("find . -name *.txt -exec grep 0 {} ;" #| "xargs test -z" #&& "echo 0-free" #|| "echo 0-exists").! 0-exists res23: Int = 0
ここで、ディレクトリに* .txt拡張子を持つファイルがあり、それらのいずれかにファイルがある場合、テキストに0があります-コンソールに0-existsを出力し、そうでない場合は0-existsを出力します。
#&&
-前のコマンドが正しく実行されると、次のコマンドを実行します。
#||
-前のコマンドがエラーで実行された場合、次のコマンドを実行します。
私はこの機能が最も気に入っています。Scala内でLinuxのようなパイプを使用し、コード内に小さなshスクリプトを書くことができます。
I / Oストリームのオーバーライド
すべてのコードは、外部プロセスの入力/出力を再定義する機能がなければ、不便で役に立たない。
多くの場合、発生したエラーを解読するため、またはすべてが正しく機能することを確認するために、表示される情報を監視する必要があります。
ProcessBuilderトレイのrun、!、!!、linesメソッドのそれぞれで、 ProcessLoggerトレイトのインスタンスを転送できます。これにより、プログラムの出力ストリームをファイルまたは行にリダイレクトできます。
ProcessLoggerを使用して、プロセスによって印刷された行数をカウントする方法は次のとおりです。
scala> var normalLines = 0 normalLines: Int = 0 scala> var errorLines = 0 errorLines: Int = 0 scala> val countLogger = ProcessLogger(line => normalLines += 1, | line => errorLines +=1) countLogger: scala.sys.process.ProcessLogger = scala.sys.process.ProcessLogger$$anon$1@459c8859 scala> "ls" ! countLogger res0: Int = 0 scala> println("normalLines: " + normalLines + ", errorLines: " + errorLines) normalLines: 4, errorLines: 0
ProcessLoggerでは、出力ストリームをオーバーライドできます。 クラスscala.sys.process.ProcessIOは、入力と出力の両方をオーバーライドするためにも使用されます。
小さな例:
Seq("grep", "1") run new ProcessIO((output: java.io.OutputStream) => { output.write("1.txt\n2.txt\n3.txt\n11.txt".getBytes) output.close() }, (input: java.io.InputStream) => { println(Source.fromInputStream(input).mkString) input.close() }, _.close())
最初のパラメーターは、プロセスへの入力ストリームです。ここでは、初期データを書き込みます。
2番目のパラメーターは標準出力で、最後のパラメーターはエラー出力です。
パラメータは、必要なスレッドを処理する関数です。
前に、外部コマンドの実行を組み合わせて、さらに同じ記録形式を使用して、プロセスにデータを転送したり、そこからデータを読み取ったりできると述べました。
#<
メソッドを使用してファイルからプロセスにデータを転送し、
#>
メソッドを使用してデータを書き込むことができます。
scala> ("echo -e 1.txt\\n2.txt\\n3.txt" #> new java.io.File("1.txt")).! res21: Int = 0 scala> ("grep 1" #< new java.io.File("1.txt")).!! res22: String = "1.txt"
同様に、たとえば、あるファイルから別のファイルに情報をコピーできます。
scala> (new java.io.File("1.txt") #> new java.io.File("2.txt")).! res23: Int = 0 scala> "cat 2.txt".! 1.txt 2.txt 3.txt res24: Int = 0
おわりに
この記事では、Scalaで外部プロセスを操作する基本について説明しました。 Javaでこれを実装するには、大量のラッパーを作成する必要があり、その結果、そのような単純さにまだ近づくことができませんでした。 APIの詳細については、 http: //www.scala-lang.orgを参照するか、ソースを掘り下げてください (たとえば、そこからいくつかの例を取り上げました)。jdk1.7では、クラスjava.lang.ProcessBuilderを少し拡張し、 Javaは、外部コマンドの実行と実行がより便利になりました。 しかし、Scalaの単純さのために、jdkは遠く離れています。