Windows cmd.exe中的管道在进程完成之前不会转发标准输出?

时间:2014-02-05 01:02:09

标签: windows batch-file delay pipe buffered

考虑Windows命令shell cmd.exe中的管道:

C:\>feed | filter

在喂料过程完成之后,喂料过程的标准输出似乎没有达到过滤过程的标准输入。

对于长时间运行的进给过程,这种类型的“缓冲”会导致输出消息出现恼人的延迟(您可能希望在早期故障时按'ctrl-c'中断它)。

有没有办法避免这种情况,以便在数据可用时,进料过程的标准输出达到过滤过程的标准输入? (没有缓冲)

例如,以下简化示例:

feed.bat:

@echo off
echo something
sleep 3
echo something else

filter.bat:

@echo off
for /F "tokens=*" %%a in ('more') do (
    echo _%%a
)

以下命令在3秒后(睡眠完成时)才会显示任何内容:

C:\>feed | filter
_something
_something else

期望的行为是打印'_something',然后延迟3秒,然后打印'_something'。

1 个答案:

答案 0 :(得分:4)

管道在Windows cmd.exe中是异步的。在将信息传递到右侧之前,他们不会等待左侧完成。但是你的程序没有证明这有两个原因。

1)在IN()子句中的命令完成之前,FOR / F命令不会开始迭代任何行。对于所有FOR / F变体都是如此。在迭代任何行之前,会缓冲IN()子句的整个结果。

所以你的filter.bat可能无法证明管道的异步特性。

2)MORE命令不会写入部分行 - 它会在打印到stdout之前等到收到换行符。 (除非它到达文件末尾)。

如果你想真正看到管道的异步特性,最好使用一个程序从stdin读取每个字符并立即将其写回stdout。


这是我的FEED.BAT版本 - 它会写多行并暂停多次。它还会在没有换行的情况下写入三个字符,并在每个字符后暂停一次。

@echo off
echo something
timeout /nobreak 3 >nul
echo something else
timeout /nobreak 3 >nul
for /l %%N in (1 1 3) do (
  <nul set /p "=%%N"
  timeout /nobreak 3 >nul
)
echo(
echo Done

这是我的FILTER.JS版本 - 它从stdin中读取一个字符并将其写入stdout直到它到达文件末尾。

while (!WScript.StdIn.AtEndOfStream) WScript.Stdout.Write(WScript.StdIn.Read(1));

这是测试行为的命令

feed | cscript //nologo filter.js

这是输出,只要在更多输出之前有暂停,就会插入<pause>

something
<pause>something else
1<pause>2<pause>3<pause>
Done

我上面的测试表明管道会立即发送它收到的任何信息(假设过滤器已准备好接收它)。

进料器和/或过滤器的设计可能会掩盖自由流动的行为。您的原始测试在过滤器中存在瓶颈,因为它在继续之前等待所有输入。进料器也可以固定。有些程序有缓冲输出。在缓冲区已满,或刷新缓冲区或流关闭之前,进纸器可能不会发送数据。

Windows管道有许多特殊的行为。我建议阅读Why does delayed expansion fail when inside a piped block of code?的所有答案,以便很好地概述许多非直观问题。