標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

os.exec の (Command) Start で、ドライブ毎のカレントディレクトリが継承されない

カレントディレクトリは Windows/UNIX ともに1プロセスにつき一つだけ存在するということになっている。だが、DOSの頃はドライブごとにカレントディレクトリが設けられていたことを覚えている人も多いだろう。そして、現在も CMD.EXE ではあたかもドライブごとにカレントディレクトリがあるかのような振る舞いをする。親のCMD.EXE の各ドライブのカレントディレクトリは全て子プロセスの CMD.EXE にも引き継がれる。これはどういう原理だろうか。

CMD.EXE では、ドライブ毎のカレントドライブを「=C:」といったイコールで始まる名前の環境変数で、子プロセスに伝播させている。

だが、Go言語の外部プロセス実行メソッド、os.exec の (Command)Start では、内部で使われている「重複する名前の環境変数を削除する関数 dedupEnvCase」が誤動作して、2つ以上のドライブのカレントディレクトリが伝播しなくなっているようだ。

go/src/os/exec/exec.go :

func dedupEnvCase(caseInsensitive bool, env []string) []string {
    out := make([]string, 0, len(env))
    saw := map[string]int{} // key => index into out
    for _, kv := range env {
        eq := strings.Index(kv, "=")
        if eq < 0 {
            out = append(out, kv)
            continue
        }
        k := kv[:eq]
        if caseInsensitive {
            k = strings.ToLower(k)
        }
        if dupIdx, isDup := saw[k]; isDup {
            out[dupIdx] = kv
            continue
        }
        saw[k] = len(out)
        out = append(out, kv)
    }
    return out
}

これ「if eq < 0」を「if eq <= 0」にしたら直るんだけどな…

回避方法としては os.exec.Cmd クラスを使わず、os.ProcessStart 関数を直接使えばよい。os.exec.Cmd は、dedupEnvCase を読んで環境変数を整理してから os.ProcessStart を読んでいるので、同じようなことをすればよい。こういうとき、ライブラリのソースが全て公開されているのはありがたい。