为什么在管道代码块内延迟扩展会失败?
这是一个简单的批处理文件,演示了如果延迟扩展位于正在通过管道传输的块内,延迟扩展将如何失败。 (失败是在脚本的末尾)谁能解释这是为什么?
我有一个解决方法,但它需要创建一个临时文件。我最初在处理 在 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
Here is a simple batch file that demonstrates how delayed expansion fails if it is within a block that is being piped. (The failure is toward the end of the script) Can anyone explain why this is?
I have a work-around, but it requires creation of a temporary file. I initially ran into this problem while working on Find files and sort by size in a Windows batch file
@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
Here are the results
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
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
正如 Aacini 所示,管道中似乎很多事情都会失败。
但实际上,了解管道的工作原理只是一个问题。
管道的每一端都在自己的异步线程中启动自己的 cmd.exe。
这就是为什么很多东西看起来都被破坏的原因。
但有了这些知识,您就可以避免这种情况并创造新的效果
2019-08-15 更新:
正如在涉及管道时,为什么在搜索字符串中具有变量扩展的“findstr”会返回意外结果?, 仅当该命令是 cmd.exe 的内部命令、该命令是批处理文件或者该命令包含在括号内的块中时,才使用 cmd.exe。未括在括号内的外部命令将在新进程中启动,无需 cmd.exe 的帮助。
编辑:深入分析
As dbenham 显示,管道两侧的膨胀阶段是等效的。
主要规则似乎是:
正常的批处理解析器阶段已完成
.. 扩展百分比
..特殊字符阶段/块开始检测
.. 延迟扩展(但仅当启用延迟扩展并且它不是命令块时)
使用
C:\Windows\system32\cmd.exe /S / 启动 cmd.exe D /c"<批处理命令>"
这些扩展遵循命令行解析器的规则,而不是批处理行解析器。
.. 扩展百分比
.. 延迟扩展(但仅当启用延迟扩展时)
如果
位于括号块内,则会被修改。调用为
C:\Windows\system32\cmd.exe /S /D /c" ( echo one %cmdcmdline% & echo 2 )"
,所有换行符均更改为&< /代码> 运算符。
为什么延迟展开阶段会受到括号的影响?
我想,它不能在批处理解析器阶段扩展,因为一个块可以包含许多命令,并且延迟扩展在执行一行时生效。
显然
!var!
无法在批处理上下文中进行计算,因为这些行仅在 cmd 行上下文中执行。但为什么在这种情况下可以在批处理上下文中对其进行评估呢?
在我看来,这是一个“错误”或不一致的行为,但这不是第一个
编辑:添加 LF 技巧
作为 dbenham 显示,将所有换行更改为
&
的 cmd 行为似乎存在一些限制。这导致
C:\Windows\system32\cmd.exe /S /D /c" ( echo 7:part1 & rem This ...& echo part2 ) "
rem 会注释完整的行尾,因此即使是右括号也会丢失。
但您可以通过嵌入自己的换行来解决这个问题!
这将导致
C:\Windows\system32\cmd.exe /S /D /c" ( echo 8: part1 %cmdcmdline% & rem 这可以工作,因为它分割了命令 %LF% echo part2 )"
由于
%LF%
在解析括号时展开,生成的代码看起来像这样
%LF%
行为始终在括号内起作用,也在批处理中文件。但在“正常”行上则不然,单个
将停止对此行的解析。编辑:异步并不是全部事实
我说过两个线程都是异步的,通常这是正确的。
但实际上,当右线程未使用管道数据时,左线程可以锁定自身。
“管道”缓冲区中似乎有〜1000个字符的限制,然后线程被阻塞,直到数据被消耗。
As Aacini shows, it seems that many things fail within a pipe.
But in reality it's only a problem to understand how the pipe works.
Each side of a pipe starts its own cmd.exe in its own asynchronous thread.
That is the cause why so many things seem to be broken.
But with this knowledge you can avoid this and create new effects
Update 2019-08-15:
As discovered at Why does `findstr` with variable expansion in its search string return unexpected results when involved in a pipe?, cmd.exe is only used if the command is internal to cmd.exe, if the command is a batch file, or if the command is enclosed in a parenthesized block. External commands not enclosed within parentheses are launched in a new process without the aid of cmd.exe.
EDIT: In depth analysis
As dbenham shows, both sides of the pipes are equivalent for the expansion phases.
The main rules seems to be:
The normal batch parser phases are done
.. percent expansion
.. special character phase/block begin detection
.. delayed expansion (but only if delayed expansion is enabled AND it isn't a command block)
Start the cmd.exe with
C:\Windows\system32\cmd.exe /S /D /c"<BATCH COMMAND>"
These expansions follow the rules of the cmd-line parser, not the batch-line parser.
.. percent expansion
.. delayed expansion (but only if delayed expansion is enabled)
The
<BATCH COMMAND>
will be modified if it's inside a parenthesis block.Called as
C:\Windows\system32\cmd.exe /S /D /c" ( echo one %cmdcmdline% & echo two )"
, all newlines are changed to&
operator.Why the delayed expansion phase is affected by parenthesis?
I suppose, it can't expand in the batch-parser-phase, as a block can consist of many commands and the delayed expansion take effect when a line is executed.
Obviously the
!var!
can't be evaluated in the batch context, as the lines are executed only in the cmd-line context.But why it can be evaluated in this case in the batch context?
In my opionion this is a "bug" or inconsitent behaviour, but it's not the first one
EDIT: Adding the LF trick
As dbenham shows, there seems to be some limitation through the cmd-behaviour that changes all line feeds into
&
.This results into
C:\Windows\system32\cmd.exe /S /D /c" ( echo 7: part1 & rem This ...& echo part2 ) "
The
rem
will remark the complete line tail, so even the closing bracket is missing then.But you can solve this with embedding your own line feeds!
This results to
C:\Windows\system32\cmd.exe /S /D /c" ( echo 8: part1 %cmdcmdline% & rem This works as it splits the commands %LF% echo part2 )"
And as the
%LF%
is expanded while parsing the parentheses, the resulting code looks likeThis
%LF%
behaviour works always inside of parentheses, also in a batch file.But not on "normal" lines, there a single
<linefeed>
will stop the parsing for this line.EDIT: Asynchronously is not the full truth
I said that the both threads are asynchronous, normally this is true.
But in reality the left thread can lock itself when the piped data isn't consumed by the right thread.
There seems to be a limit of ~1000 characters in the "pipe" buffer, then the thread is blocked until the data is consumed.
我不确定是否应该编辑我的问题,或者将其发布为答案。
我已经隐约知道管道在其自己的 CMD.EXE“会话”中分别执行左侧和右侧。但阿西尼和杰布的回应迫使我真正思考和调查管道正在发生的事情。 (感谢 jeb 演示了管道输入 SET /P 时发生的情况!)
我开发了这个调查脚本 - 它有助于解释很多内容,但也演示了一些奇怪和意外的行为。我将发布脚本,然后是输出。最后我会提供一些分析。
输出
这是我测试管道左侧和右侧的
,以证明两侧的处理是对称的。测试 1 和 2 表明,在正常批处理情况下,括号对延迟扩展没有任何影响。
测试 1L、1R: 延迟扩展按预期工作。 Var2 未定义,因此 %var2% 和 !var2!输出表明命令是在命令行上下文中执行的,而不是在批处理上下文中执行的。也就是说,使用命令行解析规则而不是批量解析。 (请参阅Windows命令解释器如何(CMD.EXE) 解析脚本?) 编辑 - !VAR2!在父批处理上下文中展开
测试 2L、2R:括号禁用延迟展开! 在我看来非常奇怪和意外。 编辑 - jeb 认为这是 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 的结果提供逻辑解释,那么我将选择它作为我原来问题的答案。否则我可能会接受这篇文章作为答案(实际上更多的是对发生的事情的观察,而不是答案)编辑 - jab 钉住了它!
附录: 回应 jeb 的评论- 这里有更多证据表明批处理中的管道命令在命令行上下文中执行,而不是在批处理上下文中执行。
此批处理脚本:
给出以下输出:
最终附录
我添加了一些额外的测试和结果,展示了迄今为止的所有发现。我还演示了 FOR 变量扩展发生在管道处理之前。最后,我展示了当多行块折叠成单行时管道处理的一些有趣的副作用。
这是输出
测试 1: 和 2: 总结了所有行为,并且 %%cmdcmdline%% 技巧确实有助于演示正在发生的情况。
测试 3:演示 FOR 变量扩展仍然适用于管道块。
测试 4:/5: 和 6:/7:显示了管道与多行块一起工作的方式的有趣副作用。提防!
我必须相信在复杂的管道场景中找出转义序列将是一场噩梦。
I wasn't sure if I should edit my question, or post this as an answer.
I already vaguely knew that a pipe executes both the left and the right side each in its own CMD.EXE "session". But Aacini's and jeb's responses forced me to really think about and investigate what is happening with pipes. (Thank you jeb for demonstrating what is happening when piping into SET /P!)
I developed this investigative script - it helps explain a lot, but also demonstrates some bizarre and unexpected behavior. I'll post the script, followed by the output. Finally I will provide some analysis.
Here is the output
I tested both the left and right side of the pipe to demonstrate that processing is symmetric on both sides.
Tests 1 and 2 demonstrate that parentheses don't have any impact on delayed expansion under normal batch circumstances.
Tests 1L,1R: Delayed expansion works as expected. Var2 is undefined, so %var2% and !var2! output demonstrates that the commands are executed in a command line context, and not a batch context. In other words, command line parsing rules are used instead of batch parsing. (see How does the Windows Command Interpreter (CMD.EXE) parse scripts?) EDIT - !VAR2! is expanded in the parent batch context
Tests 2L,2R: The parentheses disable the delayed expansion! Very bizarre and unexpected in my mind. Edit - jeb considers this an MS bug or design flaw. I agree, there doesn't seem to be any rational reason for the inconsistent behavior
Tests 3L,3R:
setlocal EnableDelayedExpansion
does not work. But this is expected because we are in a command line context.setlocal
only works in a batch context.Tests 4L,4R: Delayed expansion is initially enabled, but parentheses disable it.
CMD /V:ON
re-enables delayed expansion and everything works as expected. We still have command line context and output is as expected.Tests 5L,5R: Almost the same as 4L,4R except delayed expansion is already enabled when
CMD /V:on
is executed. %var2% gives expected command line context output. But !var2! output is blank which is expected in a batch context. This is another very bizarre and unexpected behavior. Edit - actually this makes sense now that I know !var2! is expanded in the parent batch contextTests 6L,6R,7L,7R: These are analogous to tests 4L/R,5L/R except now delayed expansion starts out disabled. This time all 4 scenarios give the expected !var2! batch context output.
If someone can provide a logical explanation for results of 2L,2R and 5L,5R then I will select that as the answer to my original question. Otherwise I will probably accept this post as the answer (really more of an observation of what happens than an answer) Edit - jab nailed it!
Addendum: In response to jeb's comment - here is more evidence that piped commands within a batch execute in a command line context, not a batch context.
This batch script:
gives this output:
Final Addendum
I've added some additional tests and results that demonstrate all the findings so far. I also demonstrate that FOR variable expansion takes place before the pipe processing. Finally I show some interesting side effects of the pipe processing when a multi-line block is collapsed into a single line.
Here is the output
Tests 1: and 2: summarize all the behaviors, and the %%cmdcmdline%% trick really helps to demonstrate what is taking place.
Test 3: demonstrates that FOR variable expansion still works with a piped block.
Tests 4:/5: and 6:/7: show interesting side effects of the way pipes work with multi-line blocks. Beware!
I've got to believe figuring out escape sequences within complex pipe scenarios will be a nightmare.
有趣的事情!我不知道答案,我所知道的是,管道操作在 Windows Batch 中具有一致的失败,这些失败不应出现在原始 MS-DOS Batch 中(如果此类功能可以在旧的 MS-DOS Batch 中执行),所以我怀疑该错误是在开发新的 Windows Batch 功能时引入的。
以下是一些示例:
echo 要分配的值 | set /p var=
上一行没有将值分配给变量,因此我们必须这样修复它:
echo Value to be allocate > > temp.txt &设置 /p var=< temp.txt
另一种:
不起作用。这样修复:
但是,此方法在某些情况下确实有效;以 DEBUG.COM 为例:
上一个程序显示:
ONE TWO
在哪种情况下有效,哪种情况下无效?只有上帝(和微软)可能知道,但它似乎与新的 Windows Batch 功能有关:SET /P 命令、延迟扩展、括号中的代码块等。
编辑:异步批处理文件
注意:我修改了此部分以纠正我的错误。有关详细信息,请参阅我对 jeb 的最后评论。
正如jeb所说,管道两侧的执行创建了两个异步进程,即使不使用
START
命令,也可以执行异步线程。Mainfile.bat:
First.bat:
Second.bat:
我们可以使用此功能来开发相当于 期望应用程序(以与pexpect Phyton模块类似的方式工作)可以控制任何这样的交互式程序:
Output.bat 文件将通过分析程序的输出来实现“Expect”部分,而Input.bat 将通过向程序提供输入来实现“Sendline”部分。从输出到输入模块的向后通信将通过一个包含所需信息的文件和一个简单的信号量系统来实现,该信号量系统通过一个或两个标志文件的存在/不存在进行控制。
Funny thing! I don't know the answer, what I know is that the pipeline operation have consistent failures in Windows Batch that should not be present in original MS-DOS Batch (if such features could be executed in old MS-DOS Batch), so I suspect that the error was introduced when the new Windows Batch features were developed.
Here are some examples:
echo Value to be assigned | set /p var=
Previous line does NOT assign the value to the variable, so we must fix it this way:
echo Value to be assigned > temp.txt & set /p var=< temp.txt
Another one:
Doesn't work. Fix it this way:
However, this method DO work in certain cases; with DEBUG.COM for example:
Previous program show:
ONE TWO
In which cases works and which not? Only God (and Microsoft) may know, but it seems to be related to new Windows Batch features: SET /P command, delayed expansion, code block in parentheses, etc.
EDIT: Asynchronous Batch files
NOTE: I modified this section to correct an error of mine. See my last comment to jeb for details.
As jeb said, the execution of both sides of a pipeline create two asynchronous processes, that made possible to execute asynchronous threads even if
START
command is not used.Mainfile.bat:
First.bat:
Second.bat:
We may use this capability to develop a program equivalent to Expect application (working in a similar way of pexpect Phyton module) that could control any interactive program this way:
Output.bat file will achieve the "Expect" part by analysing the output from the program, and Input.bat will achieve the "Sendline" part by providing the input to the program. The backwards communication from Output to Input modules will be achieved via a file with the desired information and a simple semaphore system controlled via the presence/absence of one or two flag files.