Batch file 为什么延迟扩展在管道代码块中失败?

Batch file 为什么延迟扩展在管道代码块中失败?,batch-file,pipe,Batch File,Pipe,下面是一个简单的批处理文件,演示了如果延迟扩展位于正在通过管道传输的块中,它将如何失败。(失败就在脚本末尾)有人能解释为什么会这样吗 我有一个解决办法,但它需要创建一个临时文件。我最初在工作时遇到了这个问题 以下是结果 NORMAL EXPANSION TEST Unsorted works z x y Sorted works x y z --------- DELAYED EXPANSION TEST Unsorted works z x y Sorted fails !test1!

下面是一个简单的批处理文件,演示了如果延迟扩展位于正在通过管道传输的块中,它将如何失败。(失败就在脚本末尾)有人能解释为什么会这样吗

我有一个解决办法,但它需要创建一个临时文件。我最初在工作时遇到了这个问题

以下是结果

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

真有趣!我不知道答案,我知道的是,管道操作在Windows批处理中有一致的故障,而这些故障不应该出现在原始MS-DOS批处理中(如果这些功能可以在旧的MS-DOS批处理中执行),因此我怀疑该错误是在开发新的Windows批处理功能时引入的

以下是一些例子:

要分配的回显值| set/p var=

前一行没有将值分配给变量,因此我们必须通过以下方式进行修复:

(
echo Value one
echo Value two
echo Value three
) > temp.txt
call :BatchSubroutine < temp.txt
Input | anyprogram | Output
要分配的回显值>temp.txt&set/p var=

另一个:

(
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
Input | anyprogram | Output
上一期节目:

一两个

在哪种情况下有效,而在哪种情况下无效?只有上帝(和微软)可能知道,但它似乎与Windows新的批处理功能有关:SET/P命令、延迟扩展、括号中的代码块等

编辑:异步批处理文件

注意:我修改了此部分以更正我的错误。有关详细信息,请参阅我最后对jeb的评论

正如jeb所说,管道两侧的执行创建了两个异步进程,这使得即使不使用
START
命令也可以执行异步线程

Mainfile.bat:

@echo off
echo Main start. Enter lines, type end to exit
First | Second
echo Main end
第一.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
第二,蝙蝠:

@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
我们可以使用此功能开发一个程序,相当于(以类似的方式工作),可以通过以下方式控制任何交互式程序:

(
echo Value one
echo Value two
echo Value three
) > temp.txt
call :BatchSubroutine < temp.txt
Input | anyprogram | Output

Output.bat文件将通过分析程序的输出来实现“Expect”部分,Input.bat将通过向程序提供输入来实现“Sendline”部分。从输出到输入模块的反向通信将通过一个包含所需信息的文件和一个简单的信号系统实现,该信号系统通过一个或两个标志文件的存在/不存在进行控制

正如阿奇尼所展示的,似乎许多事情在管道中都失败了

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:
正如在中发现的,仅当命令是cmd.exe的内部命令、命令是批处理文件或命令包含在括号中时,才使用cmd.exe。未括在括号内的外部命令在新进程中启动,无需cmd.exe的帮助

编辑:深入分析

如dbenham所示,管道两侧在扩建阶段是等效的。
主要规则似乎是:

正常的批处理分析器阶段已完成
.. 膨胀率
.. 特殊字符相位/块开始检测
.. 延迟扩展(但仅当延迟扩展已启用且不是命令块时)

C:\Windows\system32\cmd.exe/S/D/C''启动cmd.exe

这些扩展遵循cmd行解析器的规则,而不是批处理行解析器的规则

。。膨胀率
.. 延迟扩展(但仅在启用延迟扩展时)

如果
位于括号块内,则将对其进行修改

(
echo one %%cmdcmdline%%
echo two
) | more
称为
C:\Windows\system32\cmd.exe/S/D/C“(echo one%cmdline%&echo two)”
,所有换行符都更改为
&
运算符

为什么延迟扩展阶段会受到括号的影响?
我想,它不能在批处理解析器阶段进行扩展,因为一个块可以由许多命令组成,延迟的扩展在执行一行时生效

(
set var=one
echo !var!
set var=two
) | more
显然是
!瓦尔,因为这些行仅在cmd行上下文中执行

但为什么在这种情况下可以在批处理上下文中对其进行评估

echo !var! | more
在我看来,这是一个“错误”或不恰当的行为,但这不是第一次

编辑:添加LF技巧

正如dbenham所示,cmd行为似乎存在一些限制,它将所有换行符更改为
&

(
  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%cmdline%&rem这在分割命令%LF%echo part2时起作用)

由于解析器在解析parenthise时扩展了%lf%,因此生成的代码如下所示

( echo 8: part1 & rem This works as it splits the commands 
  echo part2  )
%LF%
行为始终在括号内工作,也在批处理文件中。
但不是在“正常”行上,只有一个
将停止对此行的解析

编辑:异步不是全部事实

我说过这两个线程都是异步的,通常情况下这是正确的。
但实际上,当右线程不使用管道传输的数据时,左线程可以锁定自身。
“管道”缓冲区中似乎有大约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=
    )
)

我不确定是否应该编辑我的问题,或者将此作为答案发布

我已经模糊地知道,管道在其自己的CMD.EXE“会话”中同时执行左侧和右侧。但是Aacini和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 %%
@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