みなさん、こんにちは。

前回紹介した TACLBASE は TACL マクロサンプルの宝庫です。TACL マクロを自習する場合、このファイルを研究するのが王道です。ぜひ一度覗いてみてください。

今回の予告テーマは外部プログラムとの連携です。外部プログラムに fup を使ってみましょう。ここにパーティション化されたファイル $VOLn.SUB.FILES があるとしましょう。このファイルにパーティション毎にデータが何件あるか知りたいとしたらどうしますか。

ファイル関係の情報なので #fileinfo built-in function を使おうと思った方はセンスがよい。ただ、#fileinfo はパーティション関連の情報は扱えないのでこの場合は残念ながら不正解。ということで fup を使うしかありません。そう、stat オプションですね。

TACL> fup info $VOL0.SUB.FILES,stat
$VOL0.SUB.FILES              xx xx 2014, 14:38
    ENSCRIBE
    TYPE K
    FORMAT 1
    CODE 256
    EXT ( 20 PAGES, 20 PAGES )
    REC 16
    BLOCK  4096
    KEYLEN 2
    KEYOFF 0
    PART ( 1, $VOL1, 14 PAGES, 42 PAGES, "25" )
    PART ( 2, $VOL2, 14 PAGES, 42 PAGES, "50" )
    PART ( 3, $VOL3, 14 PAGES, 42 PAGES, "75" )
    MAXEXTENTS 16
    OWNER 205,255
    SECURITY (RWEP): NUUU
    DATA MODIF:  25 Apr 2014, 11:40
    CREATION DATE:  24 Apr 2014, 16:19
    LAST OPEN:  25 Apr 2014, 11:40
    FILE LABEL: 200 (4.9% USED)
    EOF: 12288 (1.9% USED)
    EXTENTS ALLOCATED: 1
               TOTAL     TOTAL  AVG #   AVG   AVG %
      LEVEL    BLOCKS     RECS   RECS  SLACK  SLACK  PART
        1        1         1    1.0   4065     99 $VOL0
       DATA      1        10   10.0   3884     95
       FREE      0
       BITMAP    1
        1        1         1    1.0   4065     99 $VOL1
       DATA      1        18   18.0   3740     91
       FREE      0
       BITMAP    1
       FREE      0                                $VOL2
       BITMAP    0
        1        1         1    1.0   4065     99 $VOL3
       DATA      1        14   14.0   3812     93
       FREE      0
       BITMAP    1

情報は得られましたが余分な出力が多く、見やすいとは言えません。これをマクロで整形することにしましょう。次のような形式で出すことにします。

$VOL0.SUB.FILES
 1 $VOL0       10 RECS
 2 $VOL1       18 RECS
 3 $VOL2        0 RECS
 4 $VOL3       14 RECS

マクロで処理するためには、まず画面に出力されたメッセージを TACL に取り込む必要があります。fup プログラムとの連携です。これには run オプション のひとつである outv オプション を使います。画面に結果を出力せず variable に出力する オプション です。run オプション なので、fup の起動時に指定します。少し味見してみましょう。

TACL> #push dataout
TACL> fup /outv dataout/ info $VOL0.SUB.FILES,stat

……何にも表示されませんでした。fup の出力が dataout variable に格納されたため、画面には何も出ないのです。fup の出力は複数行に亘りますが、そのすべてが dataout variable に格納されました。確かめてみましょう。

TAC> outvar dataout
↓ ここから展開結果 ---------------------------------------------
                      ⇒ ここに空白行が1行あることに注意してください
$VOL0.SUB.FILES              xx xx 2014, 14:38
    ENSCRIBE
    TYPE K
    FORMAT 1
    CODE 256
    EXT ( 20 PAGES, 20 PAGES )
    REC 16
    BLOCK  4096
    KEYLEN 2
    KEYOFF 0
    PART ( 1, $VOL1, 14 PAGES, 42 PAGES, "25" )
    PART ( 2, $VOL2, 14 PAGES, 42 PAGES, "50" )
    PART ( 3, $VOL3, 14 PAGES, 42 PAGES, "75" )
    MAXEXTENTS 16
    OWNER 205,255
    SECURITY (RWEP): NUUU
    DATA MODIF:  25 Apr 2014, 11:40
    CREATION DATE:  24 Apr 2014, 16:19
    LAST OPEN:  25 Apr 2014, 11:40
    FILE LABEL: 200 (4.9% USED)
    EOF: 12288 (1.9% USED)
    EXTENTS ALLOCATED: 1
               TOTAL     TOTAL  AVG #   AVG   AVG %
      LEVEL    BLOCKS     RECS   RECS  SLACK  SLACK  PART
        1        1         1    1.0   4065     99 $VOL0
       DATA      1        10   10.0   3884     95
       FREE      0
       BITMAP    1
        1        1         1    1.0   4065     99 $VOL1
       DATA      1        18   18.0   3740     91
       FREE      0
       BITMAP    1
       FREE      0                                $VOL2
       BITMAP    0
        1        1         1    1.0   4065     99 $VOL3
       DATA      1        14   14.0   3812     93
       FREE      0
       BITMAP    1
↑ ここまで展開結果 ---------------------------------------------

前記の fup の出力がすべて格納されていることが判りますね。

複数行格納されているということで、単独行の場合と違う点があります。もう1つ variable を宣言して

TACL> #push textcopy

それにコピーしてみましょう。

TACL> #set textcopy [dataout]

なにやらエラーになってしまいました。何故だか判りますか?

展開を理解していれば答えが判ります。dataout を展開した形を考えてみましょう。#set textcopy [dataout] が展開されるとこうなります。

#set textcopy
$VOL0.SUB.INQCMD              xx xx 2014, 14:38
    ENSCRIBE
    TYPE K
    FORMAT 1
    CODE 256
    EXT ( 20 PAGES, 20 PAGES )
    REC 16
※ 以下省略

#set textcopy の後ろが空白なのは、dataout の先頭行が空白であるためです。次行以降はそのまま残り表示されていますね。

複数行ある variable は展開時も複数行に展開されます。改行は tacl の区切り文字の1つなので、#set コマンドは dataout の中の最初改行で終端します。#set コマンドで消費されるのは先頭の1行目だけなのです。このため2行目以下はそのまま tacl に渡ってしまいます。例えば2行目はこのように打ち込んだのと同じことになります。

TACL> $VOL0.SUB.INQCMD              xx xx 2014, 14:38

もちろんコマンドとしては無効でエラーになります。3行目以下も同様で、すべてエラーになります。この動きは結構忘れてしまいがちなので頭の片隅においといてください。

さて、dataout に取り込んだ fup 出力は、全部まとめては処理できないので1行ずつ取りだします。そのために #extract built-in function を使います。類似の関数で #extract built-in function というのもあります。関数名の最後に v が付くかどうかの差ですが、それぞれ次のような使い方になります。

TACL> #set variable^to [#extract variable^from]
TACL> #extractv variable^from variable^to

1行取り出して別の variable に格納するのが #extractv で、取り出した行を展開するのが #extract です。どちらでもかまいませんが、ここでは v の付かない方を使いましょう。なお、「取り出す」という点に留意してください。取り出された1行は、元の variable^from variable から削除されるという意味です。「取り出し」を繰り返すと、最後は空になりますので、「空になるまで処理を繰り返す」処理を組む使い方になります。

まず1行取り出してみましょう。

TACL> #set textcopy [#extract dataout]
TACL> outvar textcopy

何事も起きませんね……先頭が空行だったので、空白が表示されてしまいました。もう一度やりましょう。2行目です。

TACL> #set textcopy [#extract dataout]
TACL> outvar textcopy

今度はファイル名が表示されました。fup 出力の2行目に当たる内容です。#extract/#extractv を実行するたびに dataout から1行ずつ取り出されて減っていきます。これを繰り返して1行ずつ取り出し、整形して出力するのが今回目標とするマクロになります。

もう一度出力したい表示形式を見てみましょう。

$VOL0.SUB.FILES
 1 $VOL0       10 RECS
 2 $VOL1       18 RECS
 3 $VOL2        0 RECS
 4 $VOL3       14 RECS

先頭の "$VOL0.SUB.FILES" は2行目から取得できます。2行目は

$VOL0.SUB.INQCMD              xx xx 2014, 14:38

なので、こんなロジックになります。

#set textcopy [#extract dataout]
#setmany fullname,[textcopy]
#output [fullname]

ここは説明不要ですよね。

次の "1 $VOL0 10 RECS" はいろいろなところからの寄せ集めになります。要素は4つありますね。 "1""$VOL0""10""RECS" です。ま、最後の "RECS" は単なる文字定数ですが。

最初の "1" はどうやって出せばいいでしょうか。やりたいことはこんな感じです。

integer i
i = 0
i = i + 1
print i

これを TACL に翻訳するとこうなります。

#push i
#set i 0
#set i [#compute i + 1]
#output [i]

#compute というのは四則演算を処理する built-in function です。これを使ってカウントアップすればOKですね。

$VOL0 の部分はどうでしょうか。元の fup メッセージはこうです。

        1        1         1    1.0   4065     99 $VOL0

一番最後、7番目にありますね。7番目まで項目がある行は fup メッセージの中には数えるほどしかありません。例えば上記の次の行は

       DATA      1        10   10.0   3884     95

となっていて、6項目しかありません。統計部分のタイトル行である

                     TOTAL     TOTAL  AVG #   AVG   AVG %
      LEVEL    BLOCKS     RECS   RECS  SLACK  SLACK  PART

より後ろで7番目まで項目がある行がディスク名を表示した行です。ロジックで書くとこうです。

#set textcopy [#extract dataout]
#setmany item1 item2 item3 item4 item5 item6 item7, [textcopy]
[#if not [#emptyv item7] |then|
#set diskname [item7]
#output [diskname]
]

なお、

[#if item1 = 1 |then|
#set diskname [item7]
]

でもいいじゃないかと思った人がいるかもしれませんが、これはだめです。ここは ENSCRIBE ファイルシステムの話になりますが、データ量が増えてLEVEL が2以上になると、ディスク名は次のように出力されます。

        3        1         4    4.0   3925     97 $VOL0
        2        4       441  110.3   1977     48
        1      441     90194  204.5    170      4

ディスク名を得るには7番目が空じゃない行を取ってこないといけません。

これでディスク名が取得できました。最後の "10 RECS" はどうでしょうか。次の fup メッセージはこうです。

       DATA      1        10   10.0   3884     95

先頭の項目が #DATA# である行の2番目の項目を取得すれば、それがデータ件数です。ロジックで書くとこうです。

#set textcopy [#extract dataout]
#setmany item1 item2 item3 item4 item5 item6 item7, [textcopy]
[#if item1 '=' "DATA" |then|
#set rec^count [item2]
]

なお、データがゼロ件のディスクは DATA の行もないので、その場合は件数にゼロを代入する必要があります。

以上をまとめてマクロを作りましょう。マクロ名は武骨に fdatanum とします。variable type は 例によって routine でいきます。次のロジックを macsrc に追加してください。

?section fdatanum routine
#frame
#push fname dataout textcopy diskname fullname rec^count flag i
#push item1 item2 item3 item4 item5 item6 item7

sink [#argument /value fname/ filename]
sink [#argument end]

fup /outv dataout/ info [fname],stat
#set rec^count 0
#set i 1
#set flag 0

#set textcopy [#extract dataout]
#set textcopy [#extract dataout]
#setmany fullname,[textcopy]
#output [fullname]

[#loop |do|
#set textcopy [#extract dataout]
#setmany item1 item2 item3 item4 item5 item6 item7, [textcopy]

[#if not flag and item1 '=' "LEVEL" and item2 '=' "BLOCKS" |then|
#set flag 1
]

[#if flag and not [#emptyv item7] |then|
#set diskname [item7]
]

[#if flag and item1 '=' "DATA" |then|
#set rec^count [item2]
]

[#if flag and item1 '=' "BITMAP" |then|
#output/width 2,justify right,hold/ [i]
#output/width 1,fill space,hold/
#output/width 8,hold/[diskname]
#output/width 6,justify right,hold/[rec^count]
#output/width 1,fill space,hold/
#output RECS
#set rec^count 0
#set i [#compute i + 1]
]

|until| [#emptyv dataout]
]

#pop item1 item2 item3 item4 item5 item6 item7
#pop fname dataout textcopy diskname fullname rec^count flag i
#unframe

load して

TACL> load/keep 1/$vol.subvol.macsrc

実行します。

TACL> fdatanum $VOL0.SUB.FILES

いかがですか?外部プログラムである fup とうまく連携することで、マクロによってパーティション毎のデータ件数が表示できました。

ついでに、少し捻ったパターンを紹介してみます。今のマクロは fup 起動時に outv オプションで処理結果を vaialble に取り込んでいました。ここで outv ではなく out オプションを使うと

TACL> fup /out fileout/ info $VOL0.SUB.FILES,stat

となります。この場合 fileout という名前の edit ファイルに処理結果が出力されます。その edit ファイルの内容を variable に取り込めば outv と同じことになりますよね。で、こうします。

TACL> filetovar fileout dataout

その名の通り、file to variable でコピーしてくれるマクロです。取り込んだ後は outv の時と全く同じです。まとめるとこんな感じです。

?section fdatanum routine
#frame
#push fname dataout textcopy diskname fullname rec^count flag i
#push item1 item2 item3 item4 item5 item6 item7

sink [#argument /value fname/ filename]
sink [#argument end]

fup /out fileout/ info [fname],stat
filetovar fileout dataout
#set rec^count 0
#set i 1
#set flag 0

#set textcopy [#extract dataout]
#set textcopy [#extract dataout]
#setmany fullname,[textcopy]
#output [fullname]

[#loop |do|
#set textcopy [#extract dataout]
#setmany item1 item2 item3 item4 item5 item6 item7, [textcopy]

[#if not flag and item1 '=' "LEVEL" and item2 '=' "BLOCKS" |then|
#set flag 1
]

[#if flag and not [#emptyv item7] |then|
#set diskname [item7]
]

[#if flag and item1 '=' "DATA" |then|
#set rec^count [item2]
]

[#if flag and item1 '=' "BITMAP" |then|
#output/width 2,justify right,hold/ [i]
#output/width 1,fill space,hold/
#output/width 8,hold/[diskname]
#output/width 6,justify right,hold/[rec^count]
#output/width 1,fill space,hold/
#output RECS
#set rec^count 0
#set i [#compute i + 1]
]

|until| [#emptyv dataout]
]

#pop item1 item2 item3 item4 item5 item6 item7
#pop fname dataout textcopy diskname fullname rec^count flag i
#unframe

こちらを使うメリットは、fileout が中間ファイルとして残るのでデバッグがやりやすい、といったところですか。ま、大差ありません。

filetovar があるなら逆もしかり、vartofile マクロは varialbe の内容を edit ファイルに吐き出すマクロです。vartofile variable名 ファイル名という使い方ですね。

TACL> vartofile dataout newfile

dataout に複数行格納されていても、そのすべてが newfile ファイルに吐き出されます。またファイルへは追加モードで書きますので、既存の内容が消えたりしません。大概のファイルI/Oならこの2つのマクロでできてしまいます。

この filetovar とか vartofile を研究すると、TACL におけるファイルI/O機能の全貌がつかめます。今回の主題から逸れてしまうので、外部プログラム連携の話が終わったら改めて掘り下げてみることにします。

今回は外部プログラムの出力結果を TACL に取り込む話でしたが、次回は逆に外部プログラムに TACL からコマンドを与える話をしてみましょう。めったに見られない技が登場しますよ。Au revoir!