web-dev-qa-db-ja.com

パイプされたコードブロック内で遅延拡張が失敗するのはなぜですか?

これは、パイプされているブロック内にある場合に遅延拡張がどのように失敗するかを示す簡単なバッチファイルです。 (失敗はスクリプトの終わりに近づいています)これがなぜであるかを誰かが説明できますか?

回避策はありますが、一時ファイルを作成する必要があります。私は最初に作業中にこの問題に遭遇しました ファイルを検索し、Windowsバッチファイルでサイズで並べ替えます

@echo off
setlocal enableDelayedExpansion

set test1=x
set test2=y
set test3=z

echo(

echo NORMAL EXPANSION TEST
echo Unsorted works
(
  echo %test3%
  echo %test1%
  echo %test2%
)
echo(
echo Sorted works
(
  echo %test3%
  echo %test1%
  echo %test2%
) | sort

echo(
echo ---------
echo(

echo DELAYED EXPANSION TEST
echo Unsorted works
(
  echo !test3!
  echo !test1!
  echo !test2!
)
echo(
echo Sorted fails
(
  echo !test3!
  echo !test1!
  echo !test2!
) | sort
echo(
echo Sort workaround
(
  echo !test3!
  echo !test1!
  echo !test2!
)>temp.txt
sort temp.txt
del temp.txt

結果は次のとおりです

NORMAL EXPANSION TEST
Unsorted works
z
x
y

Sorted works
x
y
z

---------

DELAYED EXPANSION TEST
Unsorted works
z
x
y

Sorted fails
!test1!
!test2!
!test3!

Sort workaround
x
y
z
32
dbenham

Aaciniが示すように、パイプ内で多くのことが失敗しているようです。

_echo hello | set /p var=
echo here | call :function
_

しかし実際には、パイプがどのように機能するかを理解することだけが問題です。

パイプの各側は、独自の非同期スレッドで独自のcmd.exeを開始します。
それが、多くのことが壊れているように見える原因です。

しかし、この知識があれば、これを回避して新しい効果を生み出すことができます

_echo one | ( set /p varX= & set varX )
set var1=var2
set var2=content of two
echo one | ( echo %%%var1%%% )
echo three | echo MYCMDLINE %%cmdcmdline%%
echo four  | (cmd /v:on /c  echo 4: !var2!)
_

更新2019-08-15:
検索文字列に変数展開を含む `findstr`がパイプに関与しているときに予期しない結果を返すのはなぜですか? 、cmd.exeのみが使用されますコマンドがcmd.exeの内部にある場合、コマンドがバッチファイルである場合、またはコマンドが括弧で囲まれたブロックで囲まれている場合。括弧で囲まれていない外部コマンドは、cmd.exeを使用せずに新しいプロセスで起動されます。

編集:詳細分析

Dbenhamが示すように、パイプの両側は拡張フェーズで同等です。
主なルールは次のようです。

通常のバッチパーサーフェーズが実行されます
..パーセント拡張
..特殊文字フェーズ/ブロック開始検出
..遅延拡張(ただし、遅延拡張が有効で、コマンドブロックではない場合のみ)

_C:\Windows\system32\cmd.exe /S /D /c"<BATCH COMMAND>"_でcmd.exeを起動します
これらの拡張は、バッチ行パーサーではなく、コマンドラインパーサーの規則に従います。

..パーセント拡張
..遅延拡張(ただし、遅延拡張が有効になっている場合のみ)

_<BATCH COMMAND>_は、括弧ブロック内にある場合に変更されます。

_(
echo one %%cmdcmdline%%
echo two
) | more
_

C:\Windows\system32\cmd.exe /S /D /c" ( echo one %cmdcmdline% & echo two )"と呼ばれ、すべての改行は_&_演算子に変更されます。

遅延拡張フェーズが括弧の影響を受けるのはなぜですか?
ブロックは多くのコマンドで構成され、行が実行されると遅延拡張が有効になるため、バッチパーサーフェーズでは拡張できないと思います。

_(
set var=one
echo !var!
set var=two
) | more
_

明らかに、行はコマンドラインコンテキストでのみ実行されるため、_!var!_はバッチコンテキストで評価できません。

しかし、なぜこの場合、バッチコンテキストで評価できるのでしょうか。

_echo !var! | more
_

私の意見では、これは「バグ」または一貫性のない動作ですが、最初の動作ではありません

編集:LFトリックの追加

Dbenhamが示すように、すべての改行を_&_に変更するcmd-behaviourにはいくつかの制限があるようです。

_(
  echo 7: part1
  rem This kills the entire block because the closing ) is remarked!
  echo part2
) | more
_

これにより、
C:\Windows\system32\cmd.exe /S /D /c" ( echo 7: part1 & rem This ...& echo part2 ) "
remは完全な行の末尾を示しているため、閉じ括弧もありません。

しかし、独自のラインフィードを埋め込むことでこれを解決できます!

_set LF=^


REM The two empty lines above are required
(
  echo 8: part1
  rem This works as it splits the commands %%LF%% echo part2  
) | more
_

これにより、C:\Windows\system32\cmd.exe /S /D /c" ( echo 8: part1 %cmdcmdline% & rem This works as it splits the commands %LF% echo part2 )"になります。

また、パーサーによる括弧の解析中に%lf%が展開されると、結果のコードは次のようになります。

_( echo 8: part1 & rem This works as it splits the commands 
  echo part2  )
_

この_%LF%_の動作は、バッチファイルでも常に括弧内で機能します。
ただし、「通常の」行ではなく、単一の_<linefeed>_がこの行の解析を停止します。

編集:非同期は完全な真実ではありません

私は両方のスレッドが非同期であると言いました、通常これは本当です。
しかし実際には、パイプされたデータが右のスレッドによって消費されていない場合、左のスレッドはそれ自体をロックできます。
「パイプ」バッファには最大1000文字の制限があるようです。その後、データが消費されるまでスレッドはブロックされます。

_@echo off
(
    (
    for /L %%a in ( 1,1,60 ) DO (
            echo A long text can lock this thread
            echo Thread1 ##### %%a > con
        )
    )
    echo Thread1 ##### end > con
) | (
    for /L %%n in ( 1,1,6) DO @(
        ping -n 2 localhost > nul
        echo Thread2 ..... %%n
        set /p x=
    )
)
_
39
jeb

質問を編集するか、回答として投稿するかがわかりませんでした。

パイプがそれぞれ独自のCMD.EXE「セッション」で左側と右側の両方を実行することを私はすでに漠然と知っていました。しかし、Aaciniとjebの反応により、私はパイプで何が起こっているのかを本当に考えて調査する必要がありました。 (SET/Pにパイプするときに何が起こっているかを示してくれてありがとうjeb!)

私はこの調査スクリプトを開発しました。これは多くのことを説明するのに役立ちますが、奇妙で予期しない動作も示しています。スクリプトを投稿し、続いて出力を投稿します。最後に、いくつかの分析を提供します。

@echo off
cls
setlocal disableDelayedExpansion
set var1=value1
set "var2="
setlocal enableDelayedExpansion

echo on
@echo NO PIPE - delayed expansion is ON
echo 1: %var1%, %var2%, !var1!, !var2!
(echo 2: %var1%, %var2%, !var1!, !var2!)

@echo(
@echo PIPE LEFT SIDE - Delayed expansion is ON
echo 1L: %%var1%%, %%var2%%, !var1!, !var2! | more
(echo 2L: %%var1%%, %%var2%%, !var1!, !var2!) | more
(setlocal enableDelayedExpansion & echo 3L: %%var1%%, %%var2%%, !var1!, !var2!) | more
(cmd /v:on /c echo 4L: %%var1%%, %%var2%%, !var1!, !var2!) | more
cmd /v:on /c echo 5L: %%var1%%, %%var2%%, !var1!, !var2! | more
@endlocal
@echo(
@echo Delayed expansion is now OFF
(cmd /v:on /c echo 6L: %%var1%%, %%var2%%, !var1!, !var2!) | more
cmd /v:on /c echo 7L: %%var1%%, %%var2%%, !var1!, !var2! | more

@setlocal enableDelayedExpansion
@echo(
@echo PIPE RIGHT SIDE - delayed expansion is ON
echo junk | echo 1R: %%var1%%, %%var2%%, !var1!, !var2!
echo junk | (echo 2R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | (setlocal enableDelayedExpansion & echo 3R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | (cmd /v:on /c echo 4R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | cmd /v:on /c echo 5R: %%var1%%, %%var2%%, !var1!, !var2!
@endlocal
@echo(
@echo Delayed expansion is now OFF
echo junk | (cmd /v:on /c echo 6R: %%var1%%, %%var2%%, !var1!, !var2!)
echo junk | cmd /v:on /c echo 7R: %%var1%%, %%var2%%, !var1!, !var2!


これが出力です

NO PIPE - delayed expansion is ON

C:\test>echo 1: value1, , !var1!, !var2!
1: value1, , value1,

C:\test>(echo 2: value1, , !var1!, !var2! )
2: value1, , value1,

PIPE LEFT SIDE - Delayed expansion is ON

C:\test>echo 1L: %var1%, %var2%, !var1!, !var2!   | more
1L: value1, %var2%, value1,


C:\test>(echo 2L: %var1%, %var2%, !var1!, !var2! )  | more
2L: value1, %var2%, !var1!, !var2!


C:\test>(setlocal enableDelayedExpansion   & echo 3L: %var1%, %var2%, !var1!, !var2! )  | more
3L: value1, %var2%, !var1!, !var2!


C:\test>(cmd /v:on /c echo 4L: %var1%, %var2%, !var1!, !var2! )  | more
4L: value1, %var2%, value1, !var2!


C:\test>cmd /v:on /c echo 5L: %var1%, %var2%, !var1!, !var2!   | more
5L: value1, %var2%, value1,


Delayed expansion is now OFF

C:\test>(cmd /v:on /c echo 6L: %var1%, %var2%, !var1!, !var2! )  | more
6L: value1, %var2%, value1, !var2!


C:\test>cmd /v:on /c echo 7L: %var1%, %var2%, !var1!, !var2!   | more
7L: value1, %var2%, value1, !var2!


PIPE RIGHT SIDE - delayed expansion is ON

C:\test>echo junk   | echo 1R: %var1%, %var2%, !var1!, !var2!
1R: value1, %var2%, value1,

C:\test>echo junk   | (echo 2R: %var1%, %var2%, !var1!, !var2! )
2R: value1, %var2%, !var1!, !var2!

C:\test>echo junk   | (setlocal enableDelayedExpansion   & echo 3R: %var1%, %var2%, !var1!, !var2! )
3R: value1, %var2%, !var1!, !var2!

C:\test>echo junk   | (cmd /v:on /c echo 4R: %var1%, %var2%, !var1!, !var2! )
4R: value1, %var2%, value1, !var2!

C:\test>echo junk   | cmd /v:on /c echo 5R: %var1%, %var2%, !var1!, !var2!
5R: value1, %var2%, value1,

Delayed expansion is now OFF

C:\test>echo junk   | (cmd /v:on /c echo 6R: %var1%, %var2%, !var1!, !var2! )
6R: value1, %var2%, value1, !var2!

C:\test>echo junk   | cmd /v:on /c echo 7R: %var1%, %var2%, !var1!, !var2!
7R: value1, %var2%, value1, !var2!

パイプの左側と右側の両方をテストして、処理が両側で対称であることを示しました。

テスト1と2は、括弧が通常のバッチ環境での遅延拡張に影響を与えないことを示しています。

テスト1L、1R:遅延拡張は期待どおりに機能します。 Var2は未定義なので、%var2%と!var2!出力は、コマンドがバッチコンテキストではなく、コマンドラインコンテキストで実行されることを示しています。つまり、バッチ解析の代わりにコマンドライン解析ルールが使用されます。 ( Windowsコマンドインタープリター(CMD.EXE)はスクリプトをどのように解析しますか?EDIT-!VAR2!を参照してください。親バッチコンテキストで展開されます

テスト2L、2R:括弧は遅延拡張を無効にします!私の心の中で非常に奇妙で予想外です。 編集-jebは、これをMSのバグまたは設計上の欠陥と見なします。私は同意します、一貫性のない行動の合理的な理由はないようです

テスト3L、3R:setlocal EnableDelayedExpansionは機能しません。しかし、これはコマンドラインコンテキストにいるために予想されます。 setlocalはバッチコンテキストでのみ機能します。

テスト4L、4R:遅延拡張は最初は有効になっていますが、括弧は無効になっています。 CMD /V:ONは遅延拡張を再度有効にし、すべてが期待どおりに機能します。コマンドラインコンテキストはまだあり、出力は期待どおりです。

テスト5L、5R:CMD /V:onの実行時に遅延拡張がすでに有効になっていることを除いて、4L、4Rとほぼ同じです。 %var2%は、期待されるコマンドラインコンテキスト出力を提供します。しかし!var2!出力は空白であり、バッチコンテキストで予期されます。これは、もう1つの非常に奇妙で予期しない動作です。 編集-実際、これは私が!var2!を知っているので意味があります。親バッチコンテキストで展開されます

テスト6L、6R、7L、7R:これらはテスト4L/R、5L/Rに類似していますが、遅延拡張が無効になり始めます。今回は、4つのシナリオすべてで、期待される!var2!が得られます。バッチコンテキスト出力。

誰かが2L、2Rと5L、5Rの結果について論理的な説明を提供できるなら、私はそれを私の元の質問への答えとして選択します。そうでなければ、私はおそらくこの投稿を答えとして受け入れるでしょう(答えよりも実際に何が起こるかについての観察の方が多いです)編集-ジャブはそれを釘付けにしました!


補遺:jebのコメントに応えて、バッチ内のパイプされたコマンドがバッチコンテキストではなくコマンドラインコンテキストで実行されるという証拠がさらにあります。

このバッチスクリプト:

@echo on
call echo batch context %%%%
call echo cmd line context %%%% | more

この出力を与えます:

C:\test>call echo batch context %%
batch context %

C:\test>call echo cmd line context %%   | more
cmd line context %%



最終補遺

これまでのすべての調査結果を示すいくつかのテストと結果を追加しました。また、パイプ処理の前にFOR変数の展開が行われることも示します。最後に、複数行のブロックが1行に折りたたまれた場合の、パイプ処理のいくつかの興味深い副作用を示します。

@echo off
cls
setlocal disableDelayedExpansion
set var1=value1
set "var2="
setlocal enableDelayedExpansion

echo on
@echo(
@echo Delayed expansion is ON
echo 1: %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^!, !var2!, ^^^!var2^^^!, %%cmdcmdline%% | more
(echo 2: %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^! !var2!, %%cmdcmdline%%) | more
for %%a in (Z) do (echo 3: %%a %%, %%var1%%, %%var2%%, !var1!, ^^^!var1^^^! !var2!, %%cmdcmdline%%) | more
(
  echo 4: part1
  set "var2=var2Value
  set var2
  echo "
  set var2
)
(
  echo 5: part1
  set "var2=var2Value
  set var2
  echo "
  set var2
  echo --- begin cmdcmdline ---
  echo %%cmdcmdline%%
  echo --- end cmdcmdline ---
) | more
(
  echo 6: part1
  rem Only this line remarked
  echo part2
)
(
  echo 7: part1
  rem This kills the entire block because the closing ) is remarked!
  echo part2
) | more

これが出力です

Delayed expansion is ON

C:\test>echo 1: %, %var1%, %var2%, !var1!, ^!var1^!, !var2!, ^!var2^!, %cmdcmdline%   | more
1: %, value1, %var2%, value1, !var1!, , !var2!, C:\Windows\system32\cmd.exe  /S /D /c" echo 1: %, %var1%, %var2%, value1, !var1!, , !var2!, %cmdcmdline% "


C:\test>(echo 2: %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )  | more
2: %, value1, %var2%, !var1!, !var1! !var2!, C:\Windows\system32\cmd.exe  /S /D /c" ( echo 2: %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )"


C:\test>for %a in (Z) do (echo 3: %a %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )  | more

C:\test>(echo 3: Z %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )  | more
3: Z %, value1, %var2%, !var1!, !var1! !var2!, C:\Windows\system32\cmd.exe  /S /D /c" ( echo 3: Z %, %var1%, %var2%, !var1!, ^!var1^! !var2!, %cmdcmdline% )"

C:\test>(
echo 4: part1
 set "var2=var2Value
 set var2
 echo "
 set var2
)
4: part1
var2=var2Value
"
var2=var2Value

C:\test>(
echo 5: part1
 set "var2=var2Value
 set var2
 echo "
 set var2
 echo --- begin cmdcmdline ---
 echo %cmdcmdline%
 echo --- end cmdcmdline ---
)  | more
5: part1
var2=var2Value & set var2 & echo
--- begin cmdcmdline ---
C:\Windows\system32\cmd.exe  /S /D /c" ( echo 5: part1 & set "var2=var2Value
var2=var2Value & set var2 & echo
" & set var2 & echo --- begin cmdcmdline --- & echo %cmdcmdline% & echo --- end cmdcmdline --- )"
--- end cmdcmdline ---


C:\test>(
echo 6: part1
 rem Only this line remarked
 echo part2
)
6: part1
part2

C:\test>(echo %cmdcmdline%   & (
echo 7: part1
 rem This kills the entire block because the closing ) is remarked!
 echo part2
) )  | more

テスト1:および2:すべての動作を要約し、%% cmdcmdline %%トリックは、実際に何が起こっているかを示すのに役立ちます。

テスト3:FOR変数の展開がパイプブロックでも機能することを示します。

テスト4:/ 5:および6:/ 7:パイプが複数行のブロックで機能する方法の興味深い副作用を示します。注意してください!

複雑なパイプシナリオ内のエスケープシーケンスを理解することは悪夢になると私は信じなければなりません。

11
dbenham

おかしい!答えはわかりません。パイプライン操作のWindowsバッチで一貫した障害が発生し、元のMS-DOSバッチには存在しないはずです(このような機能を古いMS-DOSバッチで実行できる場合)。新しいWindowsバッチ機能が開発されたときにエラーが発生したと思われます。

ここではいくつかの例を示します。

echo Value to be assigned | set /p var=

前の行では変数に値が割り当てられていないため、次のように修正する必要があります。

echo Value to be assigned > temp.txt & set /p var=< temp.txt

もう1つ:

(
echo Value one
echo Value two
echo Value three
) | call :BatchSubroutine

動作しません。この方法で修正します。

(
echo Value one
echo Value two
echo Value three
) > temp.txt
call :BatchSubroutine < temp.txt

ただし、この方法は特定の場合に機能します。たとえば、DEBUG.COMを使用します。

echo set tab=9> def_tab.bat
(
echo e108
echo 9
echo w
echo q
) | debug def_tab.bat
call def_tab
echo ONE%tab%TWO

前のプログラムショー:

ONE     TWO

どの場合に機能し、どの場合に機能しませんか?神(およびMicrosoft)だけが知っているかもしれませんが、それは新しいWindowsバッチ機能に関連しているようです:SET/Pコマンド、遅延拡張、括弧内のコードブロックなど。

編集:非同期バッチファイル

[〜#〜]注[〜#〜]:このセクションを変更して、私のエラーを修正しました。詳細については、jebへの私の最後のコメントを参照してください。

Jebが言ったように、パイプラインの両側を実行すると2つの非同期プロセスが作成され、STARTコマンドを使用しなくても非同期スレッドを実行できるようになります。

Mainfile.bat:

@echo off
echo Main start. Enter lines, type end to exit
First | Second
echo Main end

First.bat:

@echo off
echo First start

:loop
    set /P first=
    echo First read: %first%
if /I not "%first%" == "end" goto loop
echo EOF

echo First end

Second.bat:

@echo off
echo Second start

:loop
    set /P second=Enter line: 
    echo Second read: %second%
    echo/
if not "%second%" == "EOF" goto loop

echo Second end

この機能を使用して、 アプリケーションを期待するPhytonモジュールを期待する と同様の方法で動作する)と同等のプログラムを開発できます。このプログラムは、次のように対話型プログラムを制御できます。

Input | anyprogram | Output

Output.batファイルは、プログラムからの出力を分析することによって「Expect」部分を実現し、Input.batは、プログラムに入力を提供することによって「Sendline」部分を実現します。出力モジュールから入力モジュールへの逆方向通信は、必要な情報を含むファイルと、1つまたは2つのフラグファイルの有無によって制御される単純なセマフォシステムを介して実現されます。

8
Aacini