For Development HEAD DRAFTSearch (procedure/syntax/module):

9.27 gauche.process - 高レベルプロセスインタフェース

Module: gauche.process

このモジュールは、低レベルなシステムコールの上に実装された、 プロセス制御の高レベル API を提供します。 また、このモジュールは、サブプロセスに情報を送ったり、サブプロセスから 情報を受け取ったりするのに便利な「プロセスポート」を提供します。

註: このモジュールではshell-escape-stringshell-tokenize-string というユーティリティ手続きを提供していましたが、これらはテキスト操作でありサブプロセスと 直接の関係がないので、text.shに移されました。 詳しくはtext.sh - シェルのテキストユーティリティを参照してください。 互換性のため、gauche.processからもこれらの手続きはエクスポートされます。


9.27.1 サブプロセスの実行

Function: do-process cmd/args :key redirects input output error fork environment directory host sigmask on-abnormal-exit
Function: do-process! cmd/args :key redirects input output error fork environment directory host sigmask
Function: run-process cmd/args :key redirects input output error fork environment directory host sigmask wait

{gauche.process} サブプロセスで、cmd/argsに与えられたコマンドと引数を実行します。 cmd/args引数はリストで、そのcarがコマンド名を、 cdrがコマンドラインに渡す引数を指定します。

コマンド名がスラッシュを含んでいた場合、それは実行可能ファイルへの パス名と解釈されます。そうでなければ、コマンド名がPATH環境変数 にあるディレクトリから探されます。

cmd/argsの各要素は、x->stringで文字列に変換されます。

do-processは常にサブプロセスが終了するまで待ち、正常終了した場合(終了ステータスが 0の場合)に#tを返します。サブプロセスが異常終了した場合の ふるまいはキーワード引数on-abnormal-exitによって次のように決定されます。

  • 引数が#fの場合(デフォルト)、単に#fが返されます。 これはcall-with-input-processなどのデフォルト(エラーを投げる)とは 異なっていることに注意してください。do-processは、条件分岐と組み合わせて シェルスクリプトのifコマンドのように使うことを想定しています。 (if (do-process command) then-expr else-expr)は シェルのif command; then then-command; else else-command; fi のように動作します。
  • 引数が:errorの場合、<process-abnormal-exit>エラーが投げられます。
  • 引数が:exit-codeの場合、サブプロセスがゼロ以外の終了ステータスで終了したなら、 その終了コード (サブプロセスのexit()に渡された整数値) を整数で返します。 サブプロセスがシグナルにより終了した場合は <process-abnormal-exit>エラーが投げられることに注意してください。

do-process!do-processとほぼ同じですが、 プロセスが0以外の終了ステータスを返した場合に<process-abnormal-exit>エラーを 投げます。do-processon-abnormal-exit:errorを 渡した場合の振る舞いと同じです。シェルスクリプト的な仕事で、 コマンドが失敗したらそこで終わってよいといった場合に便利です。

run-processはデフォルトでサブプロセスを並行して走らせます。 つまり、直ちに返ってきます。返り値は<process>オブジェクトで、それを 用いてサブプロセスの状態を追跡できます (Process object参照)。

例えば、次の式はls -alを実行します。

(do-process '(ls -al))

lsコマンドが何らかの理由で失敗しない限り、 ls -alの出力が表示されてから、#tが返されるでしょう。

do-processはコマンドの成功失敗を真偽値で返すので、 シェルの&&||オペレータでコマンドを組み合わせるのと同じことが andorでできます。

;; shell: make && make -s check
(and (do-process '(make))
     (do-process '(make -s check)))

;; shell: mv x.tmp x.c || rm -f x.tmp
(or (do-process '(mv x.tmp x.c))
    (do-process '(rm -f x.tmp)))

代わりにrun-processを使った場合は、ls -alの終了を待たずに <process>オブジェクトが返されます。 次の式をREPLで実行したなら、おそらく lsの出力の前に<process>オブジェクトが表示されるでしょう。

(run-process '(ls -al))

戻り値の<process>オブジェクトを保持しておいて、後で process-waitを呼ぶことによって子プロセスの終了を待つことができます。 process-waitについてはProcess objectで説明します。

(let1 p (run-process '(ls -al))
  ... do some other work ...
  (process-wait p))

run-processに、プロセス終了を待つように指示することもできます。 その場合、run-processは内部でprocess-waitを呼びます。 do-processは成功/失敗しかわからないのに対し、 プロセスの終了ステータスを後で調べたい場合に便利です。

ところで、-iは虚数として読まれることに注意してください。 -iを引数として渡したい場合は文字列にするか、|-i|のように エスケープしてシンボルにする必要があります。

(run-process '(ls "-i"))

注:外部プロセスを走らせる方法にはもうひとつ、sys-systemがあります。 こちらはコマンド行を単一の文字列で渡し、シェルを呼び出して解釈させます (プロセス管理参照)。 したがって、入出力のリダイレクトや、パイプでつないだコマンド等もそのまま渡せます。 書き捨てのスクリプトを素早く書くときはそちらの方が便利でしょう。

一方、sys-systemを使う場合には、コマンドの引数を実行時に可変にしたい場合に きちんとエスケープされているかどうかを確認したり (実はそれをやってくれる shell-escape-stringという手続きがあります。text.sh - シェルのテキストユーティリティ参照)、 またsys-systemsystem(3)経由で 呼び出す/bin/shのプラットフォーム間の違いを気にしたりする必要があります。 原則として、sys-systemの使用は固定コマンドを呼び出す簡単な場合に 止めておき、他の仕事にはrun-processdo-processを使うのが良いでしょう。

注:以前のバージョンのこの手続きは引数の取りかたが若干異なっており、 例えば(run-process "ls" "-al" :wait #t)のように呼び出しました。 これはSTkとの互換性によるものです。現在でもこの呼び出しはサポートされていますが、 非推奨です。

子プロセスの実行を細かく制御するために、do-processrun-processはたくさんの キーワード引数を取ります。以下でそれらをいくつかのカテゴリに分類して説明します。

同期

Subprocess argument: wait flag

この引数はrun-processのみに渡すことができます。 flag が真の場合、run-process は内部で process-waitを呼びだし、サブプロセスが終了するまで待ちます。 そうでなければ、サブプロセスは非同期に実行され、run-process は すぐに返ります。これがデフォルトの振る舞いになります。

サブプロセスが非同期に実行している場合、その終了ステータスを回収するために 適切なタイミングで process-wait を呼ぶことは、呼び出し側の責任であることに 注意してください。

;; This returns after wget terminates.
(define p (run-process '(wget http://practical-scheme.net/) :wait #t))

;; Check the exit status
(let1 st (process-exit-status p)
  (cond [(sys-wait-exited? st)
         (print "wget exited with status " (sys-wait-exit-status st))]
        [(sys-wait-signaled? st)
         (print "wget interrupted by signal " (sys-wait-termsig st))]
        [else
         (print "wget terminated with unknown status " st)]))
Subprocess argument: on-abnormal-exit how

この引数はdo-processのみに渡すことができます。 how#fの場合(デフォルト)、 サブプロセスが異常終了(ゼロ以外の終了ステータスを返)した場合は#fを返します。 how:errorなら、その場合はエラーを投げます。

Subprocess argument: fork flag

flag が真の場合、do-processrun-process はサブプロセスを実行するために フォークします。これはデフォルトの振る舞いです。flag が偽の場合、 do-processrun-process は直接 sys-exec を呼ぶので、戻って来ません。

I/Oリダイレクト

Subprocess argument: redirects (iospec …)

子プロセスのI/Oをどのようにリダイレクトするかを指定します。 各iospecは次の形式のいずれかです。ここでfd, fd0およびfd1子プロセスにおけるファイルディスクリプタを指定する非負の整数です。

(註:簡単にコマンドを走らせて結果を文字列で得たい場合は、 process-output->stringが使えます(Process ports参照)。 また、複数のコマンドをパイプでつなぎたい場合は プロセスパイプラインの実行を見てください。)

(< fd source)

sourceは文字列、シンボル、キーワード:null、整数、入力ポートのいずれかです。

文字列の場合、それはファイル名を指定します。そのファイルが読み込み用にオープンされ、 子プロセスはfdからその内容を読み込みます。指定されたファイルが存在しないか 読み込み用にオープンできなければエラーが報告されます。

シンボルの場合、一方向のパイプが作られ、その読み出し側の端が子プロセスのfdへと 接続されます。書き込み側の端は (process-input process source) を呼び出すことで入手できます。

:nullの場合、fdはシステムのヌルデバイスからの 読み込みになります。

整数の場合は、現プロセスの読み込み用ファイルディスクリプタを指定します。 そのファイルディスクリプタの読み込み元がdupされて、子プロセスからはfdとして 見えます。

入力ポートの場合は、入力元のファイルディスクリプタがdupされて 子プロセスのfdになります。ファイルディスクリプタを持たないポートを渡した 場合はエラーになります(ポート共通の操作port-file-number参照)。

(<< fd value)
(<<< fd obj)

valueまたはobjを子プロセスの入力ファイルディスクリプタfdへの 入力とします。

<<を使う場合、valueは文字列かユニフォームベクタ (see ユニフォームベクタ)でなければなりません。その内容がそのまま 子プロセスの入力へと送られます。ユニフォームベクタはバイナリデータを渡すのに便利です。

<<<を使う場合、objには任意のSchemeオブジェクトを渡せます。 (write-to-string obj)の結果の文字列が子プロセスの入力へと送られます。

(<& fd0 fd1)

子プロセスのファイルディスクリプタfd0が、 同じく子プロセスのファイルディスクリプタfd1が指しているものと 同じ入力を参照するようにします。 <との違いに注意してください。(< 3 0)とすると、 現プロセスのstdin (ファイルディスクリプタ0) が子プロセスからはファイルディスクリプタ3 として見えるようになります。(<& 3 0)とすると、子プロセスのファイルディスクリプタ3 は子プロセスのstdinと同じものを見るようになります (それは他のiospecによって ファイル等にリダイレクトされているかもしれません)。

<&の処理の順番について、下の方にある註も参照してください。

(> fd sink)
(>> fd sink)

sinkは文字列、シンボル、キーワード:null、整数、あるいは ファイル出力ポートでなければなりません。

文字列の場合、それはファイル名です。指定されたファイルが書き込み用にオープンされ、 子プロセスのファイルディスクリプタfdからそこに書き込むことができます。 指定のファイルが既に存在している場合、>はファイルをまず空にするのに対し、 >>はファイルの後に追加します。 >>>の違いが出るのはsinkがファイルの場合のみです。

sinkがシンボルなら、一方向のパイプが作られ、その入力端が 子プロセスのfdへと繋がれます。パイプに送られた子プロセスの出力を 読み出す入力ポートは、 (process-output process sink)で得ることができます。

sink:nullなら、fdはシステムのヌルデバイスへの 書き出しになります。

整数の場合、それは現プロセスの書き込み用ファイルディスクリプタを 指定します。それがdupされて子プロセスのfdとなります。

出力ポートの場合は、出力先のファイルディスクリプタがdupされて 子プロセスのfdになります。ファイルディスクリプタを持たないポートを渡した 場合はエラーになります(ポート共通の操作port-file-number参照)。

(>& fd0 fd1)

子プロセスのファイルディスクリプタfd0が、子プロセスのファイルディスクリプタfd1 が参照するのと同じ出力先を参照するようにします。 >との違いに注意: (> 2 1)は子プロセスのstderrが現プロセスのstdout と同じところに送られるようにするのに対し、(>& 2 1)は子プロセスのstderr が子プロセスのstdoutに送られます (それは別のiospecによってリダイレクト されているかもしれません)。

;; 子プロセスのstdoutとstderrを一緒にして読み込む
(let1 p (run-process '(command arg)
                     :redirects '((>& 2 1) (> 1 out)))
  (begin0 (port->string (process-output p 'out))
          (process-wait p)))

註: 入力元や出力先にパイプを使う場合、同じ名前(シンボル)を複数の入出力に 指定することはできません。例えば次のコードはエラーとなります。

(run-process '(command) :redirects '((> 1 out) (> 2 out))) ; error!

複数の出力を一つの出力先にマージするには>&を、 一つの入力元を複数の入力で読み込みには<&を使ってください。

(run-process '(command) :redirects '((> 1 out) (>& 2 1)))

同じファイル名を複数の入出力に指定することは、Unixのシェル同様、可能です。 ただしその場合、ファイルはそれぞれの入出力ごとに別々にオープンされる ことに注意してください。例えば同じファイルを複数の出力からオープンして 書き込んだ場合、求める結果が得られないかもしれません(通常のファイルであれば、 一方の出力がもう一方の出力を上書きしてしまうでしょう)。

註: I/Oリダイレクト指定は、unixのシェルと違って、 全て同時に処理されます。つまり、以下の式はどちらも同じように、 stdoutとstderrをファイルoutに書き出す処理となります。

(run-process '(command arg) :redirects '((>& 2 1) (> 1 "out")))
(run-process '(command arg) :redirects '((> 1 "out") (>& 2 1)))

unixのシェルではリダイレクト指定は順に処理されるので、 次の二つのコマンドラインは異なる動作となります。最初の例では 子プロセスのstderrが現時点でのstdout (それは現プロセスのstdoutでもある) へと向けられ、次に子プロセスのstdoutがファイルoutに向けられます。 従ってエラーメッセージは現プロセスのstdoutへと現れます。二番目の例では 最初に子プロセスのstdoutがファイルoutへ向けられるので、 2>&1が処理される時にはstderrの行き先はやはりoutとなります。

$ command arg 2>&1 1>out
$ command arg 1>out 2>&1

do-processrun-processredirects引数の順番にかかわらず 常に後者のように動作すると言ってもよいでしょう。

もし前者の例のように子プロセスのstderrだけを親プロセスのstdoutに 向けたいのであれば、次のように>を使うことで実現できます。

(run-process '(command arg) :redirects '((> 2 1) (> 1 "out")))
Subprocess argument: input source
Subprocess argument: output sink
Subprocess argument: error sink

サブプロセスの標準入出力を制御します。 source及びsinkは、文字列、キーワード :pipe:null:merge、整数のファイルディスクリプタ、もしくはシンボルです。

実のところ、これらはredirects引数の略記にすぎません。

:input x   ≡ :redirects '((< 0 x))
:output x  ≡ :redirects '((> 1 x))
:error x   ≡ :redirects '((> 2 x))

キーワード:pipeは互換性のためだけにサポートされています。 それぞれ、シンボルstdinstdoutstderrが 指定されたかのように振る舞います。

:input :pipe   ≡ :redirects '((< 0 stdin))
:output :pipe  ≡ :redirects '((> 1 stdout))
:error :pipe   ≡ :redirects '((> 2 stderr))

すなわち、パイプが作成され、その一方が子プロセスの標準入出力のいずれかに接続されます。 もう一方の端は(process-input process)(process-output process)および (process-error process)によって得ることができます。 (process-input, process-outputname引数が 省略されるとそれぞれstdin, stdoutをデフォルトとし、 また(process-error p)(process-output p 'stderr)と 等価だからです)

キーワード:merge:errorキーワード引数のみに有効で、 :redirects '((>& 2 1))の略記になります。 すなわち、子プロセスのstderrを子プロセスのstdoutにマージします。

引数の意味の詳しい説明については上のredirectsの項を参照してください。

実行環境

Subprocess argument: environment env-list

引数は#f(デフォルト)もしくは文字列のリストでなければなりません。 文字列のリストの場合、各文字列はNAME=VALUEという形式でなければなりません。 これが#fの場合、子プロセスは現在のプロセスの環境変数を引き継ぎます。 文字列のリストの場合であれば、それが子プロセスの環境変数を指定します。 この形式はsys-environが返すものと同じなので、 容易に現在の環境変数に環境変数を加えたり取り除いたりすることができます (環境の問い合わせ参照)。

Subprocess argument: directory directory

directoryに文字列が与えられた場合、 そのディレクトリが起動されるプロセスのワーキングディレクトリとなります。 #fが与えられた場合はの引数は何もしません。 文字列か#f以外が与えられた場合、もしくは文字列が存在するディレクトリの 名前でない場合はエラーが報告されます。

hostキーワード引数も与えられている場合、この引数は リモートプロセスのワーキングディレクトリを指定します。

註: do-processrun-processdirectoryが有効な値であることを事前に チェックしますが、実際のchdir(2)exec(2)の直前に 行われます。事前のチェックにもかかわらずchdirが失敗する可能性が あります。その時点では呼び出し元にエラーを伝える 確実な方法が無いため、Gaucheは標準エラー出力にメッセージを印字して exitします。頑健なプログラムを書く場合、そのようなケースにも留意して下さい。

Subprocess argument: sigmask mask

mask<sys-sigset>のインスタンス、整数のリスト、 あるいは#fでなければなりません。 <sys-sigset>のインスタンスである場合、それが実行する プロセスのシグナルマスクになります。整数のリストの場合は各整数が マスクすべきシグナル番号とみなされます。マルチスレッドアプリケーションで run-processを使う場合はシグナルマスクを適切に設定することが重要です。 sys-execの説明を参照して下さい (プロセス管理)。

hostキーワード引数が与えられている場合は、この引数は ローカル側のプロセス(ssh)のみのシグナルマスクをセットします。

Subprocess argument: detached flag

真の値が渡されると、作られるプロセスは親プロセスのプロセスグループから 切り離され、独自のプロセスグループを作ります。 デーモンプロセスを作る際に便利です。detached引数の詳しい動作については、 sys-fork-and-exec を見てください (プロセス管理参照)。

Subprocess argument: host hostspec

この引数は、commandをリモートホストで実行させるのに使います。 hostspecの完全な構文はprotocol:user@hostname:portで、 protocol:user@:portの部分は省略可能です。

protocolはリモートに接続するプロトコルを指定します。現在のところ sshだけがサポートされており、また省略された場合もsshが 使われます。userはリモートでのユーザ名を、hostnameは リモートホスト名を指定します。portprotocolのデフォルト 以外のポートを使いたい場合に指定します。

コマンドライン引数はリモートホスト上で解釈されます。 一方、I/Oリダイレクトはローカル側で処理されす。 例えば、次のコードはリモートマシンの/foo/barの内容を読み、 それをローカルのワーキングディレクトリ内のファイルbazへとコピーします。

(do-process '(cat "bar")
            :host "remote-host.example.com"
            :directory "/foo"
            :output "baz")

9.27.2 プロセスパイプラインの実行

Function: do-pipeline commands :key input output error directory sigmask on-abnormal-exit
Function: run-pipeline commands :key input output error wait directory sigmask

{gauche.process} 複数のプロセスでパイプラインを構成するための便利関数です。例:

(do-pipeline '((ls "src/")
               (grep "\\.c$")
               (wc -l)))

これはシェルのコマンドでのパイプラインls src/ | grep '\.c$' | wc -l と同等で、srcディレクトリにあるCソースファイルの数を数えます。

commands引数はリストのリストです。 それぞれの内側のリストは do-process/run-processが受け付けるcmd/args の形式でなければなりません。少なくともひとつのコマンドが指定される必要があります。

指定されたコマンドは、最初のコマンドのstdoutが次のコマンドのstdinに、 そのコマンドのstdoutがさらに次のコマンドのstdinに、…という具合に 接続されてから、全てが並行して実行されます。 最初のコマンドのstdinはinputキーワード引数で指定された入力から供給され、 最後のコマンドのstdoutはoutputキーワード引数で指定された出力へと流れます。 これらのキーワード引数の省略時の値は、呼び出すプロセスのstdinとstdoutです。 これらキーワード引数に与えられる値については do-process/run-processの項を見てください (サブプロセスの実行)。

全てのプロセスのstderrはstderrキーワード引数で指定される出力へと流されます。 省略時は呼び出すプロセスのstderrが使われます。

do-processと同じように、do-pipelineは全てのプロセスの終了を待ち、 パイプライン末尾のプロセスが成功した(終了ステータスが0)場合に#tを、 失敗した(終了ステータスが0以外)場合に#fを返します。 ただし、on-abnormal-exitキーワード引数に:errorを渡した場合は、 末尾のプロセスが失敗した場合にエラーが投げられます。 末尾以外のプロセスの終了ステータスはprocess-waitによって回収されますが、 返り値には影響を与えず、 失敗した場合にon-abnormal-exit引数が:errorであってもエラーは投げられません。

一方、run-pipelineは末尾のプロセスの<process>オブジェクトを返します。 パイプライン中の他のプロセスは末尾プロセスにprocess-upstreams を適用すれば得られます。 デフォルトでは、run-pipelineは全てのサブプロセスをバックグラウンドで起動して 直ちに戻ってきます。返り値の末尾プロセスに対してprocess-waitを呼ぶと、 全てのサブプロセスが終了するまで待ちます。 waitキーワード引数に真の値を渡した場合は、 run-processは全てのサブプロセスの終了を待ってから戻ります。

directorysigmaskキーワード引数は全てのプロセスに適用されます。 これらの引数の説明はdo-process/run-processの項を見てください (サブプロセスの実行)。

註: Gauche 0.9.5で、run-process-pipelineというAPIを導入しました。 これは現在のrun-pipelineに似ていますが、起動したプロセスのリストを返します。 しかし使い勝手が悪いことがわかったので、run-process-pipelineは非推奨とし、 run-pipelineで置き換えることにしました。 run-process-pipelineもしばらくサポートされますが、 できるだけ早くrun-pipelineに移行してください。


9.27.3 Process object

Class: <process>

{gauche.process} 子プロセスの状態を保持するためのオブジェクト。以下で説明される run-process 手続きにより、プロセスを作ることができます。 次章で説明するプロセスポートもプロセスオブジェクトを用いています。

<process>クラスは、run-processopen-input-process-port といった高レベルAPIで作られた子プロセスの状態を管理しています。 それらの子プロセスの終了ステータスをとるには、 process-waitprocess-wait-anyといった 高レベルAPIを利用してください。これらの手続きはシステムコール以外の情報管理も 行います。sys-waitsys-waitpidといった低レベルAPIで 直接子プロセスの終了ステータスを取ると、<process>クラスの 内部状態に矛盾が生じます。

Class: <process-abnormal-exit>

{gauche.process} 主にプロセスポートユーティリティ関数で使われるコンディション型。 <error>を継承。このコンディション型は高レベルプロセスポートユーティ リティが子プロセスが非ゼロのexitステータスで終了したことを検知したとき に投げられます。

Instance Variable of <process-abnormal-exit>: process

プロセスオブジェクト。

注: Unix用語では,exitステータスにかかわらず,プロセスがcalling exit(2)を呼ぶか,main()から帰った場合を「正常な終了」と しています。コマンドによっては非ゼロのexitステータスで何らかの正常な実行結果を 示すものもあります(grep(1)など)。しかし,ほとんどのコマ ンドでは,非ゼロの exit ステータスは要求された操作が実行できなかったこ とを表わします。それゆえ上のような場合を例外的な場合として扱います。

Function: process? obj

{gauche.process} ≡ (is-a? obj <process>)

Method: process-pid (process <process>)

{gauche.process} サブプロセス process のプロセスIDを返します。

Method: process-command (process <process>)

{gauche.process} サブプロセス process 内で起動されたコマンドを返します。

Method: process-input (process <process>) :optional name
Method: process-output (process <process>) :optional name

{gauche.process} プロセスの入力もしくは出力に一方の端がつながれたパイプの、もう一方の端を取り出します。 namerun-processredirects引数に与えた 識別用の名前です。次の例を見てください。

(let1 p (run-process '(command arg)
                     :redirects '((< 3 aux-in)
                                  (> 4 aux-out)))
  (let ([auxin  (process-input p 'aux-in)]
        [auxout (process-output p 'aux-out)])
    ;; feed something to the child's input
    (display 'something auxin)
    ;; read data from the child's output
    (read-line auxout)
    ...
    )
  (process-wait p))

シンボルaux-inaux-outがパイプを識別するのに 使われています。process-inputが返すのは出力ポートであり、 process-outputが返すのは入力ポートであることに注意してください。

nameが省略された場合、process-inputstdinを、 process-outputstdoutを使います。これらは 子プロセスの標準入力/出力をそれぞれ:input :pipe/:output :pipeで リダイレクトした場合に使われる名前です。

名前に対応するパイプが無い場合は#fが返ります。

(let* ((process (run-process '("date") :output :pipe))
       (line (read-line (process-output process))))
  (process-wait process)
  line)
 ⇒ "Fri Jun 22 22:22:22 HST 2001"

processrun-pipelineの結果であった場合、 (process-input process)(process-input process 'stdin)は 少し違う振る舞いをします。 process自体はプロセスパイプラインの末尾のプロセスを表していますが、 上記二つの呼び出しに限り、 プロセスパイプラインの先頭のプロセスのstdinにつながれたパイプを返します。 これによって、パイプライン全体をひとまとめに扱うことができます。

(let1 p (run-pipeline `((cat)
                        (grep "aba"))
                       :input :pipe :output :pipe)
  (display "banana\nhabana\ntabata\ncabara\n"
           (process-input p))    ; head of the pipeline
  (close-port (process-input p))
  (process-wait p)
  (port->string (process-output p)))
  ⇒ "habana\ntabata\ncabara\n"

もしdo-pipeline/run-pipelineを使わずに 一つのプロセスの出力を別のプロセスの入力につなげたい場合、 何らかのコルーチンかスレッドで一つのプロセスの出力をアクティブに読んで もう一つのプロセスの入力へと送ることが必要になります。 control.plumbingを使えばそれは簡単にできます (control.plumbing - ポートの配管参照)。また、流れるデータをモニタするといったことも可能です。

Method: process-error (process <process>)

{gauche.process} これは(process-output process 'stderr)と等価です。

Function: process-alive? process

{gauche.process} process が生きている場合は真を返します。process-wait によって 明示的にチェックされない限り、Gauche はサブプロセスのステータスを知ることが できないことに注意してください。

Function: process-upstreams process

{gauche.process} processrun-pipelineの結果であった場合、 パイプライン中でprocessの上流にあるプロセスのリストが返ります。 processrun-pipelineの結果でなかった場合は空リストが返ります。

(define p (run-pipeline `((cat) (grep "ho") (wc)) :input :pipe))

p ⇒ #<process 20658 "wc" active>

(process-upstreams p)
  ⇒ (#<process 20656 "cat" active> #<process 20657 "grep" active>)
Function: process-list

{gauche.process} アクティブなプロセスのリストを返します。プロセスは、その終了ステータスが process-wait によって明示的に回収されない場合は、アクティブなまま 残ります。 ひとたび終了ステータスが回収され、プロセスの状態がインアクティブに 変更されると、そのプロセスはprocess-listが返すリストからは除かれます。

Function: process-wait process :optional nohang error-on-nonzero-status

{gauche.process} サブプロセス process の終了ステータスを取得し、process のstatusスロットに値を格納します。statusスロットの値は process-exit-statusで得ることができます。

デフォルトでは、この手続きはprocess が終了するまで実行を一時停止します。 しかし、nohangに真の値が与えられた場合は、processが終了して いない場合にも直ちに返ります。

オプショナル引数error-on-nonzero-statusに真の値が与えられた場合、 この手続きは得られた終了ステータスが0で無い場合に <process-abnormal-exit>エラーを投げます。

この呼び出しによってprocessの終了ステータスが実際に取得された場合は #tを、そうでなければ#fを返します。

プロセスオブジェクトがrun-pipelineによって作られたものである場合、 process-waitは、nohangに真の値が与えられなければ、 パイプラインに関わる全てのサブプロセスの終了を待ちます。 但し、error-on-nonzero-statusprocess引数、すなわちパイプラインの 最後のプロセスにしか効力を持ちません。その他のサブプロセスがステータス0以外で終了 したとしても、その事実は各サブプロセスのプロセスオブジェクトに記録されるだけで、 エラーは通知されません。

パイプラインのプロセスに対してnohangに真の値を指定した場合、 process-waitはパイプラインの上流のプロセスに対しても終了のチェックを 行い、終了していた場合は対応するプロセスオブジェクトの終了ステータスを更新しますが、 終了していないサブプロセスについてはそのままにします。 それらのサブプロセスの終了ステータスを回収するには、それぞれのプロセスオブジェクト についてprocess-waitを発行するか、下のprocess-wait-anyを使います。

Function: process-wait-any :optional nohang

{gauche.process} run-processで作られたサブプロセスのどれかの終了ステータスを取得します。 終了ステータスが取得できたプロセスのプロセスオブジェクトを返します。

真の値がnohangに与えられた場合は、どの子プロセスも終了していない場合は 直ちに#fを返します。そうでなければ、この手続きはいずれかの子プロセスが 終了するまで待ちます。

子プロセスが存在しない場合は、この手続きは直ちに#fを返します。

Function: process-wait/poll process :key interval max-wait continue-test raise-error?

{gauche.process} processの終了ステータスを、intervalナノ秒間隔でポーリングします (デフォルトは2e6ns=2msです)。processが終了していることを見つけたら 終了ステータスを回収して直ちに戻ります。

max-waitが指定されて#fでなければ、それはポーリングを続ける期間の 最大値をナノ秒単位で指定します。その期間が過ぎたら、この手続きは諦めて戻ります。 指定されないか#fの場合は、プロセスが終了するまでポーリングしつづけます。

continue-testが与えられて#fでなければ、 それは1引数の手続きでなければなりません。 ポーリングでプロセスが終了してなかったら、その度にこの手続きが呼ばれます。 引数はポーリングした回数で、0から始まりポーリングの度に1つづつ増えてゆきます。 この手続きが#fを返したら、その時点でポーリングは打ちきられ process-wait/pollは直ちに戻ります。 真の値を返したらポーリングは継続されます。

raise-error?process-waitと同じで、 processの終了ステータスが0でなければエラーを投げます。

戻り値は、プロセスが終了してステータスを回収できれば#t、 途中で諦めた場合は#fとなります。終了ステータス自体は processからprocess-exit-statusで読み出してください。

Function: process-exit-status process

{gauche.process} process-waitによって取得されたprocessの終了ステータスを 返します。processに対してprocess-waitを呼ぶ前にこの手続きを 呼んだ場合の結果は未定義です。

終了ステータスの解釈はプラットフォームに依存します。プロセスが自発的に (exitを呼んで)終了したか、それともシグナルによって終了させられたかを 確かめるにはsys-wait-exited?sys-wait-signaled?を 使ってください。また、終了コードもしくは終了と原因となったシグナルを 知るにはsys-wait-exit-statussys-wait-termsig 使ってください (プロセス管理参照)。

Function: process-send-signal process signal

{gauche.process} サブプロセス process にシグナル signal を送ります。 signal は正確整数のシグナルナンバーでなければなりません。 シグナルの定義済み変数については、シグナルを参照して下さい。

Function: process-kill process
Function: process-stop process
Function: process-continue process

{gauche.process} それぞれ、process に、SIGKILL、SIGSTOP、SIGCONT を送ります。

Function: process-shutdown process :key ask ask-interval ask-retry signals signal-interval

processを、徐々に強力な方法を使って終了させようとします。

ask引数は、#fでなければ、試行回数を引数にとる手続きでなければなりません。 ask手続きはprocessに何らかの方法で作用して終了を試みるものです (例えば、通信チャネルからexitコマンドを送る等)。 askを呼び出し後(戻り値は捨てられます)、プロセスの状態が検査され、 プロセスがexitしたら直ちにprocess-shutdown#tを返します。 プロセスがすぐにexitしなければ、ask-intervalナノ秒おきにask手続きが 最大retry回まで呼び出されます。askに渡される引数は呼び出しの度に インクリメントされます。 ask引数に#fが渡された場合、このステップは省かれます。 省略時のデフォルト値は、ask#fask-intervalが50e6 ns (50ms)、 ask-retryが1です。

最初のステップでプロセスが止まらなければ、次にシグナルを送り始めます。 シグナルが送られるたびにプロセスの状態が検査され、終了していれば直ちに process-shutdown#tを返します。 どのシグナルが送られるかはsignals引数で決まり、 またシグナルを送る間隔はsignal-intervalで指定されます。 signalsのデフォルト値は(list SIGTERM SIGTERM SIGKILL)signal-intervalのデフォルト値は50e6 ns (50ms)です。

全てを試みてもまだプロセスが終了していなければ、#fが返されます。

(ヒント: プロセスが終了するまでシグナルを送りつづけたければ、 signalsに循環リストを渡すことで実現できます)


9.27.4 Process ports

Function: open-input-process-port command :key input error encoding conversion-buffer-size

{gauche.process} command を子プロセスで非同期に実行します。 走らせた子プロセスの標準出力につながれた入力ポートと、 プロセスオブジェクトの二つの値を返します。

commandは文字列か、コマンド名と引数のリストか、「コマンド名と引数のリスト」のリストです。

文字列の場合、それは/bin/shに渡されます。 環境変数の置換やグロブパターン、リダイレクトなどのシェルの機能が 文字列中で使えます。 文字列をつなぎ合わせてコマンドラインを作成する場合、 特殊文字をシェルに解釈してほしくなければ、それを正しくエスケープするのは 呼び出し元の責任です。text.shshell-escape-stringは 助けになるかもしれません(text.sh - シェルのテキストユーティリティ参照)。

commandがリスト(ただしリストのリストではない)の場合は、 各要素がx->stringで文字列に 変換された後に、sys-execを使って直接コマンドを起動します (リストのcarがコマンドのパス名とargv[0]の両方に使われます)。 シェルの介入を避けたい場合はこの形式を使うと良いでしょう。 特殊文字をエスケープする必要はありません。

デフォルトでは、子プロセスの標準入力は/dev/nullにリダイレクトされ、 標準エラー出力は呼び出したプロセスと共有されます。 inputerrorキーワード引数にパス名を与えることで、 これらの出力をリダイレクトすることができます。

commandがリストのリストの場合は、コマンドパイプラインが作られます (run-pipeline, プロセスパイプラインの実行参照)。 内側のリストは、コマンドパスとそのコマンドへの引数で、x->stringで文字列に 変換された後、sys-execに渡されます。 最後のコマンドの標準出力を、返されるポートから読み出すことができます。 最初のコマンドの標準入力は、inputキーワード引数にパス名が渡されれば そのファイルから、そうでなければ/dev/nullから供給されます。 各コマンドの標準エラー出力は、errorキーワード引数にパス名が渡されればそのファイルに、 そうでなければ呼び出したプロセスの標準エラー出力に流れます。

また、プロセスの出力の文字エンコーディングを指定するために encodingキーワード引数を与えることもできます。 それがutf-8でなければ、 open-input-process-portは文字コード変換ポートを挿入します。 encodingが与えられた場合、conversion-buffer-sizeキーワード引数で 変換バッファの大きさを指定することも可能です。文字コード変換の詳細については gauche.charconv - 文字コード変換を参照して下さい。

(receive (port process) (open-input-process-port "ls -l Makefile")
  (begin0 (read-line port)
          (process-wait process)))
 ⇒ "-rw-r--r--   1 shiro    users        1013 Jun 22 21:09 Makefile"

(receive (port process) (open-input-process-port '(ls -l "Makefile"))
  (begin0 (read-line port)
          (process-wait process)))
 ⇒ "-rw-r--r--   1 shiro    users        1013 Jun 22 21:09 Makefile"

(open-input-process-port "command 2>&1")
 ⇒ ;the port reads both stdout and stderr

(open-input-process-port "command 2>&1 1>/dev/null")
 ⇒ ;the port reads stderr

サブプロセスの終了ステータスは自動的に回収されません。 process-wait を呼ぶことは呼び出し側の責任であり、これを怠ると サブプロセスはゾンビプロセスになります。それが面倒であれば、以下の 手続きを使うことができます。

Function: call-with-input-process command proc :key input error encoding conversion-buffer-size on-abnormal-exit

{gauche.process} 子プロセスでcommand を実行し、その標準出力と入力ポートを パイプで繋ぎ、そのポートを引数として proc を呼び出します。 proc が返るとその終了ステータスを回収し、proc が返した 結果を返します。proc がエラーを通知しても、クリーンアップは 行われます。

キーワード引数on-abnormal-exitは子プロセスが0以外の終了ステータス を返した場合の振舞いを指定します。その値は:error(デフォルト)、 :ignore#f、もしくは一引数の手続きでなければなりません。 値が:errorの場合、0以外の終了ステータスは <process-abnormal-exit>エラーコンディションを発生させます。 コンディションオブジェクトのprocessスロットには子プロセスオブジェクトが 保持されます。値が:ignoreの場合、0以外の終了ステータスに対して 特別なアクションは取られません。値が#fの場合、プロセスの終了ステータスが 0以外であれば、procの結果は捨てられ、#fが返されます。 値が手続きの場合、0以外の終了ステータスに対して 子プロセスオブジェクトを引数にしてその手続きが呼ばれます。その手続きが 戻れば、call-with-input-processは正常動作と同じように戻ります。

on-abnormal-exitの意味がdo-processとは異なることに注意してください (サブプロセスの実行参照)。この手続きを使う場合は、 単にサブプロセスの成功/失敗を知るだけでなく、より細かな制御を必要とする場合が多いからです。

commandおよび他のキーワード引数の意味はopen-input-process-portと 同じです。

(call-with-input-process "ls -l *"
  (lambda (p) (read-line p)))
Function: with-input-from-process command thunk :key input error encoding conversion-buffer-size on-abnormal-exit

{gauche.process} 子プロセスで command を実行し、コマンドの標準出力に 接続された現在の入力ポートとともに thunk を呼び出します。 thunkが終了するかエラーを投げた後に、コマンドの終了ステータスが 回収されます。

commandおよびキーワード引数の意味はcall-with-input-processと 同じです。

(with-input-from-process "ls -l *" read-line)
Function: open-output-process-port command :key output error encoding conversion-buffer-size

{gauche.process} 子プロセスで command を非同期に実行します。 子プロセスの標準入力に接続された出力ポートと、 プロセスオブジェクトの二つの値を返します。

command引数、およびencodingconversion-buffer-sizeの 意味は、open-input-process-portと同じです。

デフォルトでは、子プロセスの標準出力は/dev/nullにリダイレクトされ、 標準エラー出力は呼び出したプロセスと共有されます。 outputerrorキーワード引数にパス名を与えることで、 これらの出力をリダイレクトすることができます。

サブプロセスの終了ステータスは自動的には回収されません。 適切なタイミングで、サブプロセスに対して process-wait を呼ぶ 必要があります。

Function: call-with-output-process command proc :key output error encoding conversion-buffer-size on-abnormal-exit

{gauche.process} command を子プロセスで実行し、コマンドの標準入力に 接続された出力ポートとともに proc を呼び出します。 コマンドの終了ステータスは、proc が返るかエラーを通知した 後に回収されます。

キーワード引数の意味はopen-output-process-portと同じです。 ただしon-abnormal-exitについてはcall-with-input-process で説明したのと同じ意味です。

(call-with-output-process "/usr/sbin/sendmail"
  (lambda (out) (display mail-body out)))
Function: with-output-to-process command thunk :key output error encoding conversion-buffer-size on-abnormal-exit

{gauche.process} コマンドの標準入力に接続された出力ポートが、thunk の実行中は 現在の出力ポートにセットされることを除いて、call-with-output-process と同じです。

Function: call-with-process-io command proc :key error encoding conversion-buffer-size on-abnormal-exit

{gauche.process} command をサブプロセスで実行し、proc を2つの引数と ともに呼び出します。最初の引数は入力ポートで、コマンドの標準出力に 接続されたものです。2番目の引数は出力ポートでコマンドの標準入力に 接続されたものです。コマンドからのエラー出力は、errorキーワード 引数でパス名が指定されない限り、呼び出したプロセスのエラー出力が共有されます。

コマンドの終了ステータスは、procが戻るかエラーを投げた場合に 回収されます。

Function: process-output->string command :key error encoding conversion-buffer-size on-abnormal-exit
Function: process-output->string-list command :key error encoding conversion-buffer-size on-abnormal-exit

{gauche.process} command を実行し、その(標準出力への)出力を回収して返します。 process-output->stringcommand からの全ての出力を連結し 1つの文字列とします。その際、空白文字からなるシーケンスは1つの空白に 置換されます。このアクションは、シェルスクリプトにおける「コマンド置換」 に似たものです。 process-output->string-listcommand からの出力を行ごとに 回収し、それらをリストにしたものを返します。改行文字は削除されます。

内部的には、commandcall-with-input-process により 実行されます。キーワード引数はcall-with-input-processに そのまま渡されます。

シェルのコマンド置換と同じように使いたい場合、 通常は :on-abnormal-exit :ignore を指定するのが良いでしょう。 ただし、ひとつコマンドを試してそれが失敗したら別のを試す、という場合は :on-abnormal-exit #f の方が有用です。:ignoreだと コマンドが何も出力せず失敗した場合でも空文字列が返るからです。

(ヒント: 子プロセスのstderr出力も一緒に結果として得たい場合は、 :error引数に:mergeを渡します。 詳しくは上のrun-processの項を見てください。)

(process-output->string '(uname -smp))
  ⇒ "Linux i686 unknown"

(process-output->string '(ls))
  ⇒ "a.out foo.c foo.c~ foo.o"

(process-output->string-list '(ls))
  ⇒ ("a.out" "foo.c" "foo.c~" "foo.o")

;; Suppress error message when the-file doesn't exist:
(process-output->string-list '(cat "the-file") :error :null)

;; If the-file doesn't exit, want to try another-file:
(any (^[flie] (process-output->string-list `(cat ,file)
                 :error :null :on-abnormal-exit #f))
     '("the-file" "another-file"))

9.27.5 Process connection

Class: <process-connection>

外部プロセスとの通信を<connection>として見せるクラスです。 コネクションインタフェースについてはgauche.connection - コネクションフレームワークを参照してください。

これを使うとコネクションを期待しているコードに外部プロセスを渡せます。 例えばネットワークと通信する部分を、直接の通信のかわりにリモートサーバの間に フィルタプロセスを入れる、といったことが簡単にできます。

Function: make-process-connection process-or-spec

外部プロセスを起動し、その標準入出力とコネクションとして通信する コネクションオブジェクトを返します。

process-or-specにはコマンドと引数からなるリストか、 <process>オブジェクトを渡すことができます。 <process>オブジェクトならそのプロセスの標準入力と標準出力はパイプに なっていなければなりません。 リストの場合は、それがrun-processに渡されて新たなプロセスが起動されます。

コネクションの入出力チャネルを両方ともシャットダウンすれば、プロセスは終了します。 多くのプロセスは、標準入力からEOFを読むと終了するので、 まずは標準入力へのチャネルをシャットダウンしてから少し待ちます。 もしそれでプロセスが終了する気配を見せなかった場合、シグナルSIGTERMを送り、 それでも終了しなかったらSIGKILLを送ります。

コネクションをクローズするだけではプロセスは終了しません。 forkしたプロセスがまだ通信するかもしれないからです。



For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT