为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

猛跑小猪 2019-06-29 10:26:06
为什么延迟扩展失败时,在管道内的代码块?下面是一个简单的批处理文件,它演示了如果延迟扩展是在被管道传输的块内,那么延迟扩展是如何失败的。(失败在脚本的末尾)有人能解释为什么会这样吗?我有一个工作,但它需要创建一个临时文件。我最初是在工作时遇到这个问题的。在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
查看完整描述

3 回答

?
当年话下

TA贡献1890条经验 获得超9个赞

正如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!)

编辑:深度分析

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

正常的批处理解析器阶段已经完成。
.。百分比膨胀
.。特殊字符相位/块开始检测
.。延迟扩展(但只在启用延迟扩展且不是命令块的情况下)

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

.。百分比膨胀
.。延迟扩展(但前提是启用延迟扩展)

这个<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!不能在批处理上下文中计算,因为这些行仅在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 %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=
    )
)


查看完整回答
反对 回复 2019-06-29
?
Smart猫小萌

TA贡献1911条经验 获得超7个赞

我不确定我是应该编辑我的问题,还是把它作为答案发布。

我已经模糊地知道,管道在自己的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)如何解析脚本?编辑-!VAR 2!在父批处理上下文中展开。

测试2L,2R:括号禁用延迟扩展!我心里很奇怪也很意外。编辑-Jeb认为这是一个MS错误或设计缺陷。我同意,不一致的行为似乎没有任何合理的理由

测试3L,3R: setlocal EnableDelayedExpansion不起作用。但这是预期的,因为我们处于命令行上下文中。setlocal只在批处理上下文中工作。

测试4L,4R:延迟扩展最初启用,但括号禁用它。CMD /V:ON重新启用延迟扩展,一切都按预期进行。我们仍然有命令行上下文,输出和预期的一样。

测试5L,5R:几乎与4L一样,除了延迟扩展之外,在以下情况下已启用了4R:CMD /V:on被处决了。%var2%提供预期的命令行上下文输出。但是!变了!输出为空白,这在批处理上下文中是预期的。这是另一个非常奇怪和意外的行为。编辑-实际上这是有意义的,现在我知道了!变种2!在父批处理上下文中展开。

测试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 %%



最后增编

我增加了一些额外的测试和结果,以证明到目前为止的所有发现。我还演示了可变膨胀发生在管道处理之前。最后,我展示了管道处理的一些有趣的副作用,当多行块被折叠成一行时。

@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:演示变量展开仍然适用于管道块。

测试4:/5:和6:/7:显示管道与多行块的工作方式的有趣的副作用。小心!

我必须相信,在复杂的管道场景中找出转义序列将是一场噩梦。


查看完整回答
反对 回复 2019-06-29
  • 3 回答
  • 0 关注
  • 625 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信