为什么延迟扩展在管道代码块内部失败?

dbe*_*ham 31 pipe batch-file

这是一个简单的批处理文件,它演示了如果延迟扩展在正在通过管道传输的块中,它将如何失败.(失败是在脚本的末尾)任何人都可以解释为什么这是?

我有一个解决方法,但它需要创建一个临时文件.我最初在查找文件时遇到了这个问题,并在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
Run Code Online (Sandbox Code Playgroud)

结果如下

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
Run Code Online (Sandbox Code Playgroud)

jeb*_*jeb 39

正如Aacini所说,似乎许多事情都在管道中失败了.

echo hello | set /p var=
echo here | call :function
Run Code Online (Sandbox Code Playgroud)

但实际上,了解管道是如何工作的只是一个问题.

管道的每一侧都在自己的ascynchronous线程中启动自己的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!)
Run Code Online (Sandbox Code Playgroud)

编辑:深入分析

正如dbenham所示,管道的两侧对于膨胀阶段是等效的.
主要规则似乎是:

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

使用C:\Windows\system32\cmd.exe /S /D /c"<BATCH COMMAND>"
这些扩展启动cmd.exe 遵循cmd-line解析器的规则,而不是批处理行解析器.

..扩展百分比
..延迟扩展(但只有在启用延迟扩展的情况下)

<BATCH COMMAND>如果它在括号内,将被修改.

(
echo one %%cmdcmdline%%
echo two
) | more
Run Code Online (Sandbox Code Playgroud)

被称为C:\Windows\system32\cmd.exe /S /D /c" ( echo one %cmdcmdline% & echo two )",所有换行都改为&运营商.

延迟扩张阶段为何受括号影响?
我想,它不能在批处理解析器阶段扩展,因为一个块可以包含许多命令,并且延迟扩展在执行一行时生效.

(
set var=one
echo !var!
set var=two
) | more
Run Code Online (Sandbox Code Playgroud)

显然,!var!无法在批处理上下文中评估,因为这些行仅在cmd-line上下文中执行.

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

echo !var! | more
Run Code Online (Sandbox Code Playgroud)

在我的观点中,这是一个"错误"或不一致的行为,但它不是第一个

编辑:添加LF技巧

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

(
  echo 7: part1
  rem This kills the entire block because the closing ) is remarked!
  echo part2
) | more
Run Code Online (Sandbox Code Playgroud)

这导致进入
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
Run Code Online (Sandbox Code Playgroud)

结果是 C:\Windows\system32\cmd.exe /S /D /c" ( echo 8: part1 %cmdcmdline% & rem This works as it splits the commands %LF% echo part2 )"

并且当解析器解析parenthises时扩展%lf%,结果代码看起来像

( echo 8: part1 & rem This works as it splits the commands 
  echo part2  )
Run Code Online (Sandbox Code Playgroud)

%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=
    )
)
Run Code Online (Sandbox Code Playgroud)

  • @jeb:你是对的!管道双方的执行都是异步的!请参阅我的答案中的附录(每次都会变得奇怪......) (2认同)

dbe*_*ham 11

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

我已经模糊地知道管道在其自己的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!
Run Code Online (Sandbox Code Playgroud)


这是输出

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!
Run Code Online (Sandbox Code Playgroud)

我测试了管道的左侧和右侧,以证明两侧的加工是对称的.

测试1和2表明,在正常批次情况下,括号对延迟扩展没有任何影响.

测试1L,1R:延迟扩展按预期工作.Var2未定义,因此%var2%和!var2!输出演示命令在命令行上下文中执行,而不是批处理上下文.换句话说,使用命令行解析规则而不是批量解析.(请参阅Windows命令解释器(CMD.EXE)如何解析脚本?)编辑 - !VAR2!在父批处理上下文中展开

测试2L,2R:括号禁用延迟扩展!在我的脑海里非常离奇和意外.编辑 - 杰布认为这是一个MS错误或设计缺陷.我同意,这种不一致的行为似乎没有任何合理的理由

测试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的结果提供合理的解释,那么我将选择它作为我原始问题的答案.否则我可能会接受这个帖子作为答案(更多的是观察发生的事情而不是答案)编辑 - 刺戳它!


附录:回应jeb的评论 - 这里有更多证据表明批处理中的管道命令在命令行上下文中执行,而不是批处理上下文.

这个批处理脚本:

@echo on
call echo batch context %%%%
call echo cmd line context %%%% | more
Run Code Online (Sandbox Code Playgroud)

给出这个输出:

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

C:\test>call echo cmd line context %%   | more
cmd line context %%
Run Code Online (Sandbox Code Playgroud)



最终附录

我添加了一些额外的测试和结果,证明了迄今为止的所有发现.我还演示了在管道处理之前进行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
Run Code Online (Sandbox Code Playgroud)

这是输出

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
Run Code Online (Sandbox Code Playgroud)

测试1:和2:总结所有行为,%% cmdcmdline %%技巧确实有助于演示正在发生的事情.

测试3:证明FOR变量扩展仍然适用于管道块.

测试4:/ 5:和6:/ 7:显示管道与多线块一起工作的有趣副作用.谨防!

我必须相信在复杂的管道场景中找出逃逸序列将是一场噩梦.


Aac*_*ini 8

有趣的事!我不知道答案,我所知道的是,管道操作在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

另一个:

(
echo Value one
echo Value two
echo Value three
) | call :BatchSubroutine
Run Code Online (Sandbox Code Playgroud)

不行.以这种方式修复它:

(
echo Value one
echo Value two
echo Value three
) > temp.txt
call :BatchSubroutine < temp.txt
Run Code Online (Sandbox Code Playgroud)

但是,这种方法在某些情况下可以工作; 以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
Run Code Online (Sandbox Code Playgroud)

以前的节目显示:

ONE     TWO

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

编辑:异步批处理文件

注意:我修改了这一部分以纠正我的错误.有关详细信息,请参阅我对jeb的最新评论.

正如jeb所说,管道两端的执行创建了两个异步进程,即使START没有使用命令也可以执行异步线程.

Mainfile.bat:

@echo off
echo Main start. Enter lines, type end to exit
First | Second
echo Main end
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

我们可以使用此功能开发一个与Expect应用程序等效的程序(以类似于pexpect Phyton模块的方式工作),可以通过这种方式控制任何交互式程序:

Input | anyprogram | Output
Run Code Online (Sandbox Code Playgroud)

Output.bat文件将通过分析程序的输出来实现"Expect"部分,并且Input.bat将通过向程序提供输入来实现"Sendline"部分.从输出到输入模块的向后通信将通过具有所需信息的文件和通过存在/不存在一个或两个标志文件来控制的简单信号量系统来实现.