[Windows] パイプでつないだコマンドはサブシェルで実行される

2017年4月24日月曜日

Windows コマンドプロンプト

echo foo | set /p x= で x は設定できない

[Windows] コマンドプロンプトで文字列入出力のTipsでは改行なしのメッセージを set /p を使って出力した。
>set /p x=hello<nul & echo world
hello world

>

これを使って、別のコマンドの出力をパイプを通して変数に格納できないかなと思ってやってみたら、できなかった。
>echo foo | set /p x=

>set x
環境変数 x が定義されていません

>

うーん、なんでうまくいかないんだろう。
と思ったけど、よく考えたらパイプの先のコマンドは別プロセスで実行されるから set で環境変数を設定しても親プロセスには設定内容が反映されないんだった。

なので、こんな感じでパイプの中で変数を確認すると、ちゃんと設定されていることが分かる。
>echo foo | (set /p x=& set x)
x=foo

>set x
環境変数 x が定義されていません

>set foo="foo" | (set bar="bar" & set foo & set bar)
環境変数 foo が定義されていません
bar="bar"

>

特に set とか echo は内部コマンドであるため、cmd /c set foo="foo" として、サブシェルを経由して呼び出しているイメージになる。

パイプを使った時の cmd.exe プロセスを見てみる

試しに以下のコマンドを実行して、別コンソールでプロセスを表示してみると

コマンド実行前:
>tasklist /fi "IMAGENAME eq cmd.exe"

イメージ名                       PID セッション名     セッション# メモリ使用量
========================= ======== =============== ========= ==========
cmd.exe                       3200 Services                0      484 K
cmd.exe                       3348 Services                0      508 K
cmd.exe                      16648 Console                 1    4,500 K
cmd.exe                      13084 Console                 1    4,020 K


これを実行する。
>(timeout /t 10 /nobreak >NUL & echo foo) | set /p x=


コマンド実行中:
>tasklist /fi "IMAGENAME eq cmd.exe"

イメージ名                       PID セッション名     セッション# メモリ使用量
========================= ======== =============== ========= ==========
cmd.exe                       3200 Services                0      484 K
cmd.exe                       3348 Services                0      508 K
cmd.exe                      16648 Console                 1    4,500 K
cmd.exe                      13084 Console                 1    4,020 K
cmd.exe                       9176 Console                 1    3,684 K
cmd.exe                      14524 Console                 1    3,212 K


実行前には存在しなかった cmd.exe が2つ起動していることが分かる。

コマンド実行後:
>tasklist /fi "IMAGENAME eq cmd.exe"

イメージ名                       PID セッション名     セッション# メモリ使用量
========================= ======== =============== ========= ==========
cmd.exe                       3200 Services                0      484 K
cmd.exe                       3348 Services                0      508 K
cmd.exe                      16648 Console                 1    4,500 K
cmd.exe                      13084 Console                 1    4,020 K


実行後には実行前の cmd.exe のみが残っている。

また、パイプを使わないで以下のみを実行すると、実行中でも新しい cmd.exe は起動していないことが分かる。
>timeout /t 10 /nobreak >NUL & echo foo


コマンド実行中:
>tasklist /fi "IMAGENAME eq cmd.exe"

イメージ名                       PID セッション名     セッション# メモリ使用量
========================= ======== =============== ========= ==========
cmd.exe                       3200 Services                0      484 K
cmd.exe                       3348 Services                0      508 K
cmd.exe                      16648 Console                 1    4,500 K
cmd.exe                      13084 Console                 1    4,020 K