我不确定是编辑我的问题还是将其发布为答案。
我已经模糊地了解到,管道会在各自的CMD.EXE“会话”中执行左边和右边的命令。但是Aacini和jeb的回答迫使我真正思考并调查了管道的运作方式。(感谢jeb演示了管道转到SET / P时会发生什么!)
我编写了这个调查脚本-它有助于解释很多内容,但也展示了一些奇怪和意想不到的行为。我将发布脚本及其输出。最后我会提供一些分析。
@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)如何解析脚本?)编辑-!VAR2!在父批处理上下文中扩展
测试2L、2R:括号禁用了延迟扩展!在我看来非常奇怪和意外。编辑-jeb认为这是微软的错误或设计缺陷。我同意,似乎没有任何理性的原因解释这种不一致的行为
测试3L、3R:setlocal EnableDelayedExpansion
不起作用。但这是预期的,因为我们处于命令行上下文中。setlocal
只在批处理上下文中起作用。
测试4L、4R:延迟扩展最初是启用的,但括号禁用了它。CMD /V:ON
重新启用了延迟扩展,并且一切都按预期工作。我们仍然具有命令行上下文,输出与预期相同。
测试5L、5R:与4L、4R几乎相同,只是在执行CMD /V:on
时已经启用了延迟扩展。 %var2%给出了预期的命令行上下文输出。但是,在批处理上下文中预期为空的!var2!输出。这是另一种非常奇怪和意外的行为。编辑-现在我知道!var2!在父批处理上下文中展开,所以实际上这很有意义
测试6L、6R、7L、7R:这些类似于测试4L/R、5L/R,只是这次延迟扩展起初被禁用了。这次所有4个场景都给出了预期的!var2!批处理上下文输出。
如果有人能为2L、2R和5L、5R的结果提供逻辑解释,那么我将选择它作为我的原始问题的答案。否则,我可能会接受此帖子作为答案(实际上更多是对发生情况的观察而不是答案)编辑-jab nailed it!
附录:作为对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变量扩展在管道处理之前发生的情况。最后,我展示了当多行块折叠成单行时,管道处理产生的一些有趣的副作用。
@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展示了管道在多行块中工作的有趣副作用。小心!
我相信在复杂的管道场景中确定转义序列将是一场噩梦。
CALL
或CMD /C
调用BatSub.bat
,cd . | BatSub
在BatSub.bat
结束后也会返回到当前批处理文件(我们现在知道这里有一个隐式的CMD /C
)。此外,我们现在知道使用两个重定向com1 > file & com2 < file
比使用管道com1 | com2
更快;从现在开始我将避免使用管道而选择使用两个重定向。所有这些听起来对我来说都很奇怪! @jeb:只有一个细节,管道右侧的执行不是异步的... - Aacinirem
注释整行尾部的问题:当您使用rem/
而不是仅使用rem
时,管道代码可以正常工作,前提是备注中不包含)
或者像^^^)
这样的双重转义符号。 - aschipflrem/
没有被第二阶段中特殊的 REM-Parser 检测到,所以它被处理成一个普通命令,只有在执行阶段解析器才会检测到它只是一个 rem 并且可以被忽略。 - jeb