REXX MEMO

作成開始日 2007.09.27
最終更新日 2014.05.27


●HTMLタグ削除 (2014.09.29)

DELTAG: PROCEDURE; PARSE ARG st;
DO While (POS('<',st)>0)|(POS('>',st)>0)
  p=POS('<',st)
  q=POS('>',st);
  IF q=0 THEN q=LENGTH(st);	/* <………| ⇒ <………> */
  IF p=0 THEN p=1;		/* |………> ⇒ <………> */
  IF p>q THEN p=1;		/* |…><…| ⇒ <…><…| */
  st=SUBSTR(st,1,p-1)||SUBSTR(st,q+1)
END
RETURN st
あくまでも行単位の処理なので、タグが複数行にまたがり、尚且つ行内に「<」も「>」も含まれていない場合はお手上げ(スクリプトやcss)。逆に、ソースコードやスクリプトに「<」や「>」が含まれていると、タグと看做してしまう。この辺りは、このルーチンに掛ける前に判断して分岐処理をしないとダメだろう。


●データリストの更新 (2014.05.27)

[データ名−データ値]形式のリストが万単位であり、データが随時追加・削除される場合、リストを効率よく更新するにはどうしたら良いか? 例えば、あるディレクトリ内のファイルの[ファイル名−CRC値]のリストがあり、随時このディレクトリにファイルが追加・削除される場合; の3種類の判別が必要になる。この場合、追加・削除前のディレクトリ・リストと追加・削除後のディレクトリ・リストを比較して、差分を抽出すれば良いのだが、データ数が万単位であると、単純総当たり方式での比較には物凄い時間が掛かる。

そこで、新データリストと旧データリストの類似性が高いことを前提に、もっと効率的な差分算出の方法を考える。類似性が高いというのは、万単位のリストのうち、追加・削除されたデータはせいぜい百単位程度で、リストの並び順も基本的に同じであるような場合を指す。もし、大量のファイルの追加・削除があったなら、総当たり比較なり、完全な作り直しなりが必要になるし、リストの並び順が完全ランダムであるなら、効率的な差分算出の手掛かりがなくなる。

まず、新ディレクトリ・リストを基準に処理を始める。 新ディレクトリ・リストにはそもそも削除されたファイルは含まれないので、 3種のファイルのうち、ドロップに関してはこの時点で処理が終っている。 問題は、新規追加分と既存分の判別である。

基本的な考え方は、総当たり比較ではなく、同番比較である。 たとえば、新1番−旧1番、新2番−旧2番、新3番−旧3番…と比較する。 新旧リストがほぼ同じであるなら、おそらく、これらのデータ一発で一致する。 が、もちろん、追加・削除があるのだから、どこかで不一致個所が現れる。 たとえば新4番が追加されたデータの場合、旧4番とは一致しない。 その場合は、旧5番、旧6番……と最後まで比較を続けるが、 当然一致データは見つからないので、これは新規データであると判別できる。 ここでポイントが2つある。

一つは、新4番に一致するデータが、旧4番よりも前にある可能性はないのか? という点。 これは基本的にない。というか、ないことを前提にしたアルゴリズムである。 もし、イレギュラーな事態が発生して(手作業でリストをいじるなど)、 該当旧データが前に移動していた場合には発見できなくなるわけだが、 その場合は諦めて、追加扱いでCRCを作り直せば良い。 そのオーバーヘッドは、比較を頭からやり直すオーバーヘッドよりも遥かに小さい (4番や5番なら問題はないが、同じ事態が1万番や2万番でも起きうるので)。 それに、そんなことは滅多に起きない。

二つ目は、新4番と旧4番が不一致の段階で、即追加データと判断することは できないのか?という点。これはできない。 リストの不一致は追加だけでなく、削除でも起きるから。 仮に、新4番が既存データで、旧4番は削除されたデータの場合でも、 同じように同番でのデータの不一致が起きる。 しかし、この場合、新4番は旧5番と一致する筈である。 この可能性がある以上、「不一致即追加データ」という判断はできず、 比較を続ける必要がある。

この場合重要なのは、追加や削除がデータ番号のズレを累積していく点。 最初に、同番比較が基本と言ったが、ズレが生じれば、同番ではなく、 累積されたズレを考慮した番号での比較が必要になる。

Do n=1 to newMax; newValue.n=''; End;	/* 値の初期化 */

m0=1
Do n=1 to newMax			/* newMax、oldMaxは新旧データの数 */
  Do m=m0 to oldMax			/* m0からスタートするのがミソ */
    IF newData.n=oldData.m THEN 	/* 既存データと一致した場合 */
    Do
      newValue.n=oldValue.m		/* 既存データの値を流用 */
      m0=m+1				/* 旧データの比較開始点を次へ */
      Leave;
    End;
  End;
End;
この模擬コードでは、既存データには旧データと同じValueが与えられるが、 追加データのValueは空のままである。 逆に言えば、Valueがそのまま追加データのフラグになっているので、 Valueが空のデータのみ新たに処理すれば良い。


●サブルーチンの外部化 (2014.05.25)

単純に、サブルーチン名のREXXファイルを作り、 組み込み関数同様に呼び出せば良い。 ただし、一つの外部ファイルに複数のサブルーチンを作成することはできない。 いわゆるライブラリ化は不可能(だと思う;少なくとも私はやり方を知らない)。 また、当然パスが通ってないと使えない。 コマンドラインのときはカレントに置いてあれば良いが、 アイコンから起動する時は作業ディレクトリの設定を忘れずに。

【注意】外部サブルーチンは内部サブルーチンよりもかなり遅くなる。使用頻度の高いものは要再考。

変数は原則的に全てローカライズされる(特殊なものは除く)。 引数の受け取りはPARSE ARGで、戻り値はRETURNで指定すればよい。 なお、配列の受け渡しは不可能だが、キューで仲介する方法はある。

たとえば、外部ルーチン側でarray.1〜array.100という配列データを作成/処理した場合;

DO n=1 to 100
  QUEUE array.n
END
として、配列データをキューに入れる。 これを受け取るメインルーチン側では;
DO n=1 to 100
  PARSE PULL array.n
END
として取り出せば良い。キューはプログラム間(と言うよりシステム全体)で共有できる。

なお、プログラムの不正終了などでキューにデータが残った場合、 自動的なクリアは行われないので、 事前に次のような方法で初期化しておくと良い。

q=QUEUED(); DO n=1 TO q; PULL dmy; END;


●サブルーチンのProcedur Exposeで配列を晒す (2014.05.25)

mySub: Procedure Expose myArray.
みたいな感じで、配列名のあとに「.」を付ければ良い。


●メモリ容量をKB/MB/GB/TB単位で表示する (2014.05.15)

バイト単位のメモリ容量をKBやMBなどの単位に変換して表示する(例えば128352256→128 MB)。REXXに指数関数や対数関数があれば、もうちょっとスマートに書けるんだが…。ちなみに、以下のままだと999バイト以下はエラーになるし、有効桁数未満は切り捨てだし、小数点の位置なんかも見苦しくなることがある。以下はあくまでも原理。微調整は状況に合わせて変更のこと。
MemUnit: Procedure ;
  mem=arg(1)
  unitSt='KMGT'
  Do m=1 to 4
     mem=mem/1000
     if mem<1 then leave
  End
  mem=mem*1000
  mem=left(mem,3)||' '||substr(unitSt,m-1,1)||'B'
  Return mem
End;


●進行状況の表示 (2014.01.18)

大量のファイルをループで処理する場合、進捗状況が表示される方が安心。 表示方法はいろいろあるけれど、一応、以下の方法が便利ではないかと。 キモはSysCurPos関数で表示位置を固定すること。 進捗状況の表示によって画面がスクロールするような方法だと、 スクロールのオーバーヘッドが処理速度を極端に低下させる可能性がある。
PARSE VALUE SysCurPos() WITH raw col

Do n=1 to nMax
  IF n//100=0 THEN 		/* この場合は100回おきに表示 */
  DO
    rc=SysCurPos(raw,col)
    CALL CHAROUT ,'-proceeding ... 'n '/' nMax
  END
  ………
  ………
  ………
End


●大文字→小文字変換 (2013.10.08/2015.07.10)

小文字→大文字変換はTRANSLATET関数をパラメータなしで使えば良い。 が、その逆変換を簡単に指定する方法はないようだ。 仕方ないので、地道に変換する。
st=TRANSLATE(st,'abcdedfghijklmnopqrstuvxwyz','ABCDEDFGHIJKLMNOPQRSTUVXWYZ')
でも、これではカッコ悪いので、もう少しスマートにすると;
st=TRANSLATE(st, XRANGE('a','z'), XRANGE('A','Z'))
文字列が半角英数字のみから構成されているなら、ビット演算で;
st=BITOR(st,,'20'x)
とするのが簡単。ただし、記号は一部化けるので要注意(下記の例では、末尾の3つの記号が化けている)。
SAY BITOR('ABCabcXYZ123*+-/._^\',,'20'x)  /* → abcabcxyz123*+-/.~| */

【サンプルスクリプト】

/* LC.CMD : ファイル名小文字化スクリプト */
/* カレントディレクトリ内のファイル名をすべて小文字化する */

Call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
Call SysLoadFuncs

rc=sysfiletree('*','file','FO')
do n=1 to file.0
  fn0=file.n
  fn1=TRANSLATE(fn0, XRANGE('a','z'), XRANGE('A','Z'))
  fn1=FILESPEC('N',fn1)
  '@REN' fn0 fn1
  say fn1
end;

exit;


●REXXの自作関数の引数の受け渡し (2010.04.01)

ARGで引き数を受け取る時は、自動的に大文字に変換される。日本語を渡す時は文字が化ける可能性が大なので注意。ARGではなく、PARSE ARGで受ければ、文字は化けない。なお、2バイト文字を正常に認識させるには、先頭で「OPTIONS ETMODE」と宣言しておけばよいようだが、サテ、これ引数の受け渡しの際も有効かどうかは確認していない。


●オブジェクトのリネーム (2007.09.10)

通常、ファイル名の変更にはREN(RENAME)コマンドを使うが、RENではオブジェクト名までは変わらない。迂闊にRENを使うと、ファイル名とオブジェクト名が食い違ってしまう。オブジェクト名を変更するには、REXXのSysSetObjectData(ファイル名,"title=新オブジェクト名")を使う。この方法ならば、オブジェクト名とファイル名が同時に変更される。

たとえば、ロング名を持つJpeg画像のファイル名およびオブジェクト名を、右から8文字に切り詰めるには、次のようにする。

/* オブジェクト名変更 */
Call RxFuncAdd 'SysLoadFuncs', 'RexxUtil', 'SysLoadFuncs'
Call SysLoadFuncs

rc=SysFileTree('*.jpg',file,'OF');
Do n=1 to file.0
  fname=filespec('N',file.n);
  parse var fname body '.' ext
  newbody=right(body,8)	/* ロング名を右から8文字に切り詰める */
  newfname=newbody||'.'||ext;
  say newfname
  dataStr='title='||newfname
  rc=SysSetObjectData(file.n,dataStr);
End;
ただし、この方法は目茶苦茶時間が掛かる。何をやっているのか知らないが…。で、外道な方法としては、RENで物理名を変えて、eautil /sで拡張属性を削除する、という方法がよいかな。


●ファイルの有無(ファイルの存否)と日付、サイズの取得

ファイルの有無の確認にはSTREMA関数を使うと便利。ファイルをサーチするSysFileTreeよりも効率が良いはず。ファイル名には相対パス(例えば'..\myfile.txt')も可能なので、相対パス→絶対パスの変換にも使える。
【構文】rc=STREAM('ファイル名','c','query exists')
【戻値】存在すればrcにフルパス付きファイル名を返す
【用例】rc=STREAM('E:\ABC\file.txt','c','query exists')


この方法はディレクトリの存否確認には使えない。ディレクトリはストリームにならないため。
ディレクトリの有無の確認にはSysFileTreeを使う。

また、STREAM関数を使えば、ファイルのタイムスタンプやファイルサイズをチェックすることもできる。

【構文】rc=STREAM('ファイル名','c','query datetime')
【戻値】rcにタイムスタンプを返す

【構文】rc=STREAM('..\file.txt','c','query size')
【戻値】rcにファイルサイズを返す

●ディレクトリの存在確認

SysFileTreeで該当ディレクトリの有無を調べる、というのが基本的な方法。ファイルと違って、ディレクトリはストリームじゃないから、STREAM関数は使えない。しかし、有無を確認し、なければ作成する、ということなら、いきなりSysMkDirを使ってもいいよね−−と思ったけど、それだとエラー処理に問題が出るな。やっぱだめ。

【逆襲のOS/2目次】 【ホーム】