CLOVER🍀

That was when it all began.

いろんな言語で、外部プロセス起動

前のエントリで、Groovy/Scalaでプロセス起動に関するコードをちょこちょこと書いていたのですが、ちょっと自分が割と使う言語でまとめてみることにしました。

ほんの、サンプルです。

サンプルのお題は、

  • 外部プロセスの実行結果(戻り値)を取得し、変数retに格納する
  • 外部プロセスの出力結果(標準出力)を取得し、変数textに格納する
  • 上記2つの結果を得るために、外部プロセスを2回起動しない

という感じで。その他、補足も交えつつ。

Java

割とよく使い、そして1番面倒…。

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.IOException;

public class ProcessExec {
    public static void main(String[] args) {
        try {
            Process process = Runtime.getRuntime().exec("ls -l");
            // もしくは
            // Process process = new ProcessBuilder("ls", "-l").start();

            String text;
            try (InputStream in = process.getInputStream();
                 InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                 BufferedReader reader = new BufferedReader(isr)) {
                 StringBuilder builder = new StringBuilder();
                 int c;
                 while ((c = reader.read()) != -1) {
                     builder.append((char)c);
                 }
                 text = builder.toString();
            }

            int ret = process.waitFor();

            System.out.println("Command Text:");
            System.out.println(text);
            System.out.println("Command Return Code = " + ret);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Process#execまたはProcessBuilder#startを使います。ProcessBuilderを使う場合は、コマンドの引数などはメソッドの別の引数として与える必要があります。
標準出力吐かれる内容は、Process#getInputStreamで取得できるので、これを読み出します。

Javaの外部プロセスに関する内容については、こちらが詳しいです。
http://www.ne.jp/asahi/hishidama/home/tech/java/process.html

標準出力、標準エラー出力を扱う場合は、状況によってはスレッドを使う必要があったりします。

Groovy

Javaと同じものが使える…というのはさておき、ここではGDKが威力を発揮します。

def process = 'ls -l'.execute()
def text = process.text
def ret = process.waitFor()

println("Command Text:")
println(text)
println("Command Return Code = ${ret}")

Stringに対してexecuteメソッドが定義されていて、いきなり外部プロセスを実行できます。戻り値はProcessのインスタンスです。また、Process#getTextで標準出力に吐かれた内容が得られます。

Groovyの場合は、公式ドキュメントが参考になるでしょう。
http://groovy.codehaus.org/Process+Management

Scala

こちらもJavaと同じものが使えますが、ここではscala.sys.processパッケージを使ってみます。

import scala.sys.process._

val builder = new StringBuilder
val ret = "ls -l" ! ProcessLogger(line => {
                                    builder ++= line
                                    builder ++= System.lineSeparator
                                  }, line => ())
val text = builder toString

println("Command Text:")
println(text)
printf("Command Result = %d%n", ret)

Stringに対して!メソッドを呼んでいますが、ここでは暗黙の型変換が入っています。これが気になったり、Predefの暗黙の型変換と衝突する場合は、scala.sys.process.Processオブジェクトを直接扱いましょう。

!メソッドは引数が取れるので、ここで標準出力に吐かれた結果を読んでいます。

Scalascala.sys.processパッケージについては、以下のサイトが参考になります。
http://www.mwsoft.jp/programming/scala/scala_process.html

また、単に外部プロセスの戻り値やコマンドの出力結果が欲しい場合は、以下のコードでOKです。

import scala.sys.process._

val ret = "ls -l" !  // コマンド出力結果は標準出力へ
val text = "ls -l " !!

ところで、!や!!メソッドののすぐ次の行に続けて処理を書いたりすると、場合によっては!や!!メソッドの引数と解釈されてコンパイルエラーになったりするので、ちょっと注意しましょう…。

Clojure

こちらもJava(略)…、Clojureの場合は、clojure.java.shell名前空間のsh関数を使用します。

(use '[clojure.java.shell :only (sh)])

(let [p-result (sh "ls" "-l")]
  (println "Command Text:")
  (println (:out p-result))
  (println (format "Command Return Code = %d" (:exit p-result))))

(shutdown-agents)

結果はマップとして返ってくるので、それぞれキーワードで結果を取得します。:exitキーワードでコマンドの戻り値、:outキーワードで標準出力に吐かれた結果、:errキーワードで標準エラー出力に吐かれた結果をそれぞれ取得できます。JavaのProcessBuilderを使う時と同様、コマンド引数がある場合はそれぞれ分割する必要があります。

なお、sh関数は内部的にfuture関数を使用しているので、shutdown-agents関数を入れておかないとJavaVMが終了しません。この点には注意を。

Clojureの場合は、参考になるのはClojureDocsですかね。
http://clojuredocs.org/clojure_core/clojure.java.shell/sh

Perl

個人的には、1番使っているような気がします。

#!/usr/bin/perl

use strict;
use warnings;

my $text = `ls -l`;
my $ret = $?;
print "Command Text:\n";
print "$text\n";

print "Command Return Code = $ret\n";

``でコマンドを囲むと、外部コマンドを実行できます。結果は標準出力に吐かれた結果が返り、コマンドの戻り値自体は$?で取得できます。

なお、system関数を使っても外部コマンドを実行できます。

my $ret = system 'ls -l';  # コマンド出力結果は標準出力へ
print "Command Return Code = $ret\n";

自分の仕事では、One Linerと組み合わせて大活躍してます。

Python

普段それほど使用していないのですが、たまに遊んでみたくなるのでこれを機に調べてみました。今の推奨は、subprocessモジュールらしいです。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from subprocess import Popen, PIPE

process = Popen(["ls", "-l"], stdout = PIPE)
# 実行コマンドを文字列としたい場合は、shell=Trueとする
# process = Popen("ls -l", shell=True, stdout = PIPE)

text = ""
while True:
    line = process.stdout.readline()
    if not line:
        break
    else:
        text += line  ## 注)改行込み

ret = process.wait()
    
print "Command Text:"
print text
print "Command Return Code : ", ret

なんか、Javaな感じがしますが…。標準出力を扱う場合は、Popen関数のstdout引数にPIPEを指定します。また、実行するコマンドを引数も含めて1つの文字列として渡したい場合は、シェル経由で実行する必要があるので、shell引数にTrueを指定します。

標準出力に吐かれる結果が要らないのであれば、subprocess.callが使えます。

from subprocess import call

ret = call(["ls", "-l"])  # コマンド出力結果は標準出力へ
print "Command Return Code : ", ret

以前のPython(2.4以前)では、os.system関数を使用するそうです。

from os import system

ret = system("ls -l")  # コマンド出力結果は標準出力へ
print "Command Return Code : ", ret

その他、commandsモジュールとかもあるらしいですが、こちらもos.systemと同様今は推奨されていないので、割愛。

Pythonの外部プロセス起動で参考になるのは、まずは公式ドキュメント。
http://www.python.jp/doc/release/library/subprocess.html

それから、こちらでしょうか。
http://d.hatena.ne.jp/kakurasan/20080413/p1
http://d.hatena.ne.jp/t2y-1979/20100116/1263640894


たまにちょっとしたコードを書く時とかに、きっと見るでしょう(笑)。