†REXXのどろぬま†

コマンドライン出力の取り込み

作成開始日 2014.10.25
最終更新日 2014.10.29

想定している状況は;
  1. REXXプログラムの中から外部プログラムを呼び出して、その出力を利用したい。
  2. しかし、当該外部プログラムはコマンドラインにしか出力できない。
  3. 出力を一時ファイルにリダイレクして(DIR > TMP.TXTの要領)、メイン側から一時ファイルを開けば良いのだが、余りにスマートさに欠ける。
この方法では、STREAM関数による一時ファイルの明示的な開閉や、ファイルポインタの設定、あるいは一時ファイルの削除などが必須になる(少なくとも、すべきである)。この《一時ファイルをガチャガチャしなければならない》点が生理的に嫌。そこで、パイプを使って、もう少しスマートに処理する事にした。ただし、この方法では、コマンドライン出力とメインプログラムを仲介する第三のプログラムが必要になる。

ちなみに、LinuxのシェルスクリプトやWindowsのBATCHでは、 コマンドの標準出力を簡単に取り込めるようになっている。 たとえば、Windowsでは「FOR /F」を使う。 しかし、OS/2のバッチのFOR命令には「/F」オプションはなく、 外部プログラムの実行結果を取り込むのは基本的に不可能なようだ。

ここでは、次のような3つのプログラムを考える。

もちろん、ポイントは仲介プログラムPIPE.CMD。具体的には以下のような感じになる。
/* PIPE.CMD */

Do n=1 to 10		/* この例では10行決め打ち */
  PARSE PULL x.n	/* 外部コマンドの出力を受け取る */
End

Do n=1 to 10		/* この例では無加工で受け渡し */
  QUEUE x.n		/* 受け取った結果をキューに送る */
End
これを呼び出すメイン側では;
/* MAIN.CMD */

……
'EXTR.EXE xyz | PIPE'	/* 外部プログラムを実行して結果をPIPE.CMDに受け渡す */
			/* なお、ここで「xyz」はEXTR.EXEの引数とする        */
nMax=QUEUED()		/* この例では行数を自動検出(上記の設定ならば10)   */

Do n=1 to nMax
  PARSE PULL x.n	/* キューから結果を受け取る */
End;
のように受け取ればよい。 ちなみに、予め出力が1行だけだと判っていれば、ソースは超簡単になる。
/* PIPE.CMD */
PARSE PULL x; QUEUE x
/* MAIN.CMD */
……
'EXTR.EXE xyz | PIPE'; PARSE PULL x

●ポイント

  1. コマンドライン出力はPULLでしか受け取れない。引数ARG(1)での受け取りは不可。

  2. コマンドラインのパイプとREXXのキューは基本的に同じもの。しかし、メインプログラムから直接パイプに接続することはできない。「EXTR.EXE | MAIN」とすると、再帰呼び出しで無限ループに陥る。これを避けるために、仲介プログラムPIPEを使用している。

  3. 「PARSE PULL」の「PARSE」は必須、単なる「PULL」だと強制大文字化による2バイト文字の文字化けが発生する(文字化けはEXMODEオプションで防げるかも知れないが、そもそも大文字化の必要性はないだろう)。

  4. PULLの直後にQUEUEするとコマンドラインのパイプが壊れる。コマンドラインもREXXもともに「SESSION」というキューを使っているが、コマンドラインのパイプは特殊なようで、新たにキューにデータを追加すると、残っているデータがクリアされてしまう(QUEUEでもPUSHでも同じ)。必ずPULLが全部済んでからQUEUEすること。

  5. コマンドライン出力の終了を検知する一般的な方法は不明。QUEUED()は常に0となっている(だから新規追加で残データがクリアされるのかも?)。行数決め打ちか、特定のキーワードで判断するしかない(たとえばDIRコマンドの末尾には必ず「バイト空き」という文字列が含まれる)。(*)

  6. 出力行数を超えて読み込んでも空白行が続くだけで、REXXのキューのようにキー入力待ちにはならない。
(*) ただし、「DIR | MORE」などでは終了を正しく検知しているわけで、検知が原理的に不可能とも思えない。今後の課題。

●サンプルプログラム

以下の2本のプログラムを同じディレクトリに置いて、MAIN.CMDを実行すると、カレントドライブの空き容量が表示される。

▼PIPE.CMD

/* PIPE.CMD */
/* DIRコマンドの出力を取り込み、空き容量を表示する行のみをキューに送る */

Do Forever
  PARSE PULL st	/* コマンドライン出力の取り込み */
  IF POS('バイト空き',st)>0 THEN LEAVE;	/* 空き容量表示行検出 */
End

QUEUE st	/* キューにデータを送る */
▼MAIN.CMD
/* MAIN.CMD */
/* DIRコマンドの出力から空き容量を取得して表示する */

'DIR | PIPE'	/* 外部プログラムを実行して結果をPIPE.CMDに送る */
		/* PIPE.CMDは必要な行を選び出してキューに送る */
PARSE PULL st	/* キューからデータを受け取る */

/* 〜〜 以下は表記の整形:本題とは無関係 〜〜 */

PARSE VAR st a b c
IF b='K' then a=a*1000;			/* 1GB超えるとKB表記になる?*/

K=1024; M=K*1024; G=M*1024; 		/* 単位の定義 */
SELECT
  WHEN a>=G THEN a=(a/G)' GB'
  WHEN a>=M THEN a=(a/M)' MB'
  WHEN a>=K THEN a=(a/K)' KB'
  Othewise a=a'B'
End
SAY
SAY 'free space =' a

Exit;


【REXXのどろぬま目次】 【ホーム】