Scalaで外部プロセスを実行する

はじめに



自宅のプロジェクトの1つで、小さな外部プロセスマネージャーを作成する必要がありました。 アプリケーションは、外部デーモンを起動し、定期的にその状態を監視し、オフ、オン、設定の変更などが必要な場合に使用できる必要があります。 そのようなタスクのためのJavaの既存の機能は非常に少なく、Scalaを同時に扱っていたので、Scalaがどのようにそれを使っているかを見ることにしました。 そして、驚いたことに、Scalaは外部プロセスを操作するための優れたAPIを私の意見で提供しています。

この記事では、これについて詳しく説明したいと思います。



プロセス開始



2つの特性は、プロセスを使用した作業の基礎です 。これらはscala.sys.process.Processscala.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つのメソッドがあります。



この特性は、標準のJavaクラスjava.lang.Processに非常に似ています。

ProcessBuilder トレイでプロセスを開始するための特別な方法があります 。 主なものについて簡単に説明します。



私のプロジェクトでは、外部プロセスへのリンクを保存する必要がなかったため、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は遠く離れています。



All Articles