DOSのFORを使用したときに入るファイル名末尾の空白

あるディレクトリ下にある拡張子が.cppのファイルを表示するバッチファイルを作った。

ex1.bat

FOR /F %%f IN ('DIR /B /S') DO SET FILE=%%f && SET EXT=%%~xf && CALL :EXECUTE
GOTO :EOF

:EXECUTE
    IF "%EXT%"==".cpp" ECHO %FILE%
    GOTO :EOF

上記のプログラムは一見正しいのだけれど,実は動かないのである。色々デバッグしたところ,どうも%EXT%(そして%FILE%)の末尾に余分なスペースが入っているらしい。なぜスペースが入るのか分からなくて2時間くらいあーだこーだと苦労したのたけど,その甲斐あって原因が判明した。修正したものが次のコードである。

ex2.bat

FOR /F %%f IN ('DIR /B /S') DO SET FILE=%%f&& SET EXT=%%~xf&& CALL :EXECUTE
GOTO :EOF

:EXECUTE
    IF "%EXT%"==".cpp" ECHO %FILE%
    GOTO :EOF

「&&」の前にスペースを付けてはいけないっていうね。
ちなみにスペースを付けたければ小括弧を使ってブロック化すればいい。メンテナンス性を考えればその方がいいんかな。

パイプの方向は子→親

会社で使ってるgrepに-rオプションがなくて毎回「find . -type f | xargs grep WORD /dev/null」としていたのだけれど,めんどくさくなったのでgrepのラッパを書くことにした。ラッパの内容は親プロセスと子プロセスをパイプで繋いだ後にfindとxargsをexecvするだけなのだけれど,findを親プロセス・子プロセスのどちらで実行すべきか若干迷ったのです。
あんまりforkを使わないので間違っているのかもしれないけれど,今日考えた限りでは子プロセスでfindを使うべきかなーっと思った。理由は,findよりもxargsの方が後に終了するから。今回の場合,たいていはfindがxargsよりも先に終了する。シェルは親プロセスの終了を待っているので,親プロセスがfindを実行するとxargsの終了を待たずにシェルへ制御を返すことになるため,xargsの表示結果が乱れる場合がある。親プロセスがxargsを実行する場合,このようなことは起こらない。
正しくはwaitとかwaitpidとか使うのだと思うんだけど,シグナルのことを考え出すと正しいコードを書くことができないんよなー。子プロセスがサスペンドされたときの作法とかどうすればいいんだ……

Exec()を使うときは標準出力を先に読み込むようにする

仕事でコマンドからデータを受け取ったり加工したりしなければならなかった。ちょっとDOSスクリプトじゃ荷が重い作業でVBScriptを使ったのだけれど,VBScriptをよく知らないせいかはまってしまった。Exec() で実行した外部コマンドの標準出力を受け取って加工するだけなのだけれど,途中でプログラムが止まってしまうのである。プログラムの概要は次みたいな感じ。

Main.vbs

Option Explicit

Dim objExec

' カレントディレクトリ下のディレクトリ構造を取得する
Set objExec = CreateObject("WScript.Shell").Exec("cmd /C tree /F")

' プログラムが終了するまで待つ (終了すると Status の値が 1 になる)
Do While objExec.Status = 0
	WScript.Sleep 100
Loop

' エラーが発生していないことを確認する (正常終了時の ExitCode は 0)
If objExec.ExitCode <> 0 Then
	WScript.Echo "エラーが発生しました"
	WScript.Quit
End If

' ディレクトリ構造を tree コマンドの標準出力から読み取って書き出す。
WScript.Echo objExec.StdOut.ReadAll

実行例

C:\>cscript C:\tmp\main.vbs
Microsoft (R) Windows Script Host Version 5.7
Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.


※ いつまでたってもこの状態から進まない

どうも,標準出力へデータを大量に書き出すコマンドをExec()するとまずいらしい。事実,ファイルが1つも存在しないディレクトリ上でMain.vbsを実行すると正常に動作する。振る舞いから察するにパイプがいっぱいになって読み込み待ちが発生しているみたいなので,コマンドが標準出力に書き出したデータを読み込んでからエラーチェックしてみるとうまく動いた。
適当にプログラムを書いてパイプの詰まり具合を調べてみると,どうもバッファのサイズは4096バイトらしい。つまり,標準出力へ4096バイト以上書き出すプログラムをExec()する場合は定期的にパイプからデータを読み出すなどしてバッファが詰まらないようにする必要がある。また,標準エラー出力のバッファも4096バイトらしく標準出力の場合と同じ問題が発生する。
まー,実際のところ4096バイトくらいバッファがあれば(ウルトラ親切なエラーメッセージを表示するプログラムで無い限り)標準エラー出力が詰まることはないのだろうけど,標準出力は問題だよなー。パイプを使ったプログラムはC言語でもPerlでも書いたことあるけれど,こんなところで詰まったのは初めて。今までは単に運がよかっただけなんかなぁ。

privateなメソッドはオーバライドできる?

privateなメソッドをオーバライドすることができるか検証してみた。といってもテストコードを書いてみただけなので,厳密な定義を必要としている人はJavaの仕様書を読んだ方がいいと思う。

Main.java

abstract class Base {
    private String getName() {
        return "Base";
    }
    
    public void printName1() {
        System.out.println(getName());
    }
    
    public abstract void printName2();
}

class Derived extends Base {
    private String getName() {
        return "Derived";
    }
    
    public void printName2() {
        System.out.println(getName());
    }
}

class Main {
    public static void main(String[] args) {
        Derived derived = new Derived();
        
        derived.printName1();
        derived.printName2();
        
        ((Derived) derived).printName1();
        ((Derived) derived).printName2();
    }
}

実行結果

D:\tmp>javac Main.java

D:\tmp>java Main
Base
Derived
Base
Derived

D:\tmp>

とりあえずコンパイルは通っているのでprivateなメソッドの再定義には成功しているらしい。でも,呼び出し元の型に関わらず,printName1()では基底クラスのgetName()が,printName2()では派生クラスのgetName()が呼び出されている。つまり,privateなメソッドを呼び出す場合,呼び出されるのは呼び出し元メンバ関数が定義されているものなわけか。Javaのメソッドは全部継承可能だと思っていたけれど,そうじゃないんだなー。