为什么在管道代码块中延迟扩展会失败? [英] Why does delayed expansion fail when inside a piped block of code?

查看:26
本文介绍了为什么在管道代码块中延迟扩展会失败?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这是一个简单的批处理文件,它演示了延迟扩展如何在被管道传输的块内失败.(失败是在脚本的末尾)谁能解释一下这是为什么?

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?

我有一个变通方法,但它需要创建一个临时文件.我最初在处理 在 Windows 批处理文件中查找文件并按大小排序

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

结果如下

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

推荐答案

正如 Aacini 所示,似乎很多事情在一个管道中都失败了.

As Aacini shows, it seems that many things fail within a pipe.

echo hello | set /p var=
echo here | call :function

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

But in reality it's only a problem to understand how the pipe works.

管道的每一侧都在自己的异步线程中启动自己的 cmd.exe.
这就是为什么这么多东西似乎都坏了的原因.

Each side of a pipe starts its own cmd.exe in its own ascynchronous 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

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 年 8 月 15 日更新:
正如在 发现的那样">为什么在其搜索字符串中具有变量扩展的 `findstr` 在涉及管道时会返回意外结果?, cmd.exe 仅在命令是 cmd.exe 的内部命令、命令是批处理文件或命令包含在括号中的块中时才使用.未括在括号内的外部命令在没有 cmd.exe 帮助的情况下在新进程中启动.

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.

深入分析

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

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)

使用 C:Windowssystem32cmd.exe/S/D/c""
启动 cmd.exe这些扩展遵循 cmd 行解析器而不是批处理行解析器的规则.

Start the cmd.exe with C:Windowssystem32cmd.exe /S /D /c"<BATCH COMMAND>"
These expansions follows the rules of the cmd-line parser not the the batch-line parser.

...百分比扩张
...延迟扩展(但仅限于启用延迟扩展)

如果它在括号内,将被修改.

The <BATCH COMMAND> will be modified if it's inside a parenthesis block.

(
echo one %%cmdcmdline%%
echo two
) | more

Called as C:Windowssystem32cmd.exe/S/D/c" ( echo one %cmdcmdline% & echo two )",所有换行符都改为& 运算符.

Called as C:Windowssystem32cmd.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.

(
set var=one
echo !var!
set var=two
) | more

显然,!var! 不能在批处理上下文中计算,因为这些行仅在 cmd 行上下文中执行.

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?

echo !var! | more

在我看来,这是一个错误"或不一致的行为,但这不是第一个

In my opionion this is a "bug" or inconsitent behaviour, but it's not the first one

添加 LF 技巧

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

As dbenham shows, there seems to be some limitation through the cmd-behaviour that changes all line feeds into &.

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

这导致
C:Windowssystem32cmd.exe/S/D/c" ( echo 7: part1 & rem This ...& echo part2 ) "
rem 将注释完整的行尾,因此即使是右括号也不见了.

This results into
C:Windowssystem32cmd.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!

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:Windowssystem32cmd.exe/S/D/c" ( echo 8: part1 %cmdcmdline% & rem 这在拆分命令 %LF% echo part2 时起作用)"

并且由于解析器在解析括号时扩展了 %lf%,结果代码如下

And as the %lf% is expanded while parsing the parenthises by the parser, the resulting code looks like

( echo 8: part1 & rem This works as it splits the commands 
  echo part2  )

%LF% 行为始终在括号内起作用,也在批处理文件中起作用.
但不是在正常"行上,单个 将停止对此行的解析.

This %LF% behaviour works always inside of parenthesis, also in a batch file.
But not on "normal" lines, there a single <linefeed> will stop the parsing for this line.

异步不是全部的事实

我说两个线程都是异步的,通常是这样.
但实际上,当右线程没有使用管道数据时,左线程可以锁定自己.
管道"缓冲区中似乎有 ~1000 个字符的限制,然后线程被阻塞,直到数据被消耗.

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.

@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=
    )
)

这篇关于为什么在管道代码块中延迟扩展会失败?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆