为什么我不能按照这个顺序redirect到两个inputstream`0 <`和`3 <`?

当我尝试将多个文件redirect到多个stream时,如下例所示,一切都按预期工作:

3< stream3.txt 4< stream4.txt echo/ 

类似这样的工作也与更多的stream和文件和其他命令比echo/甚至function块的代码。 即使使用保留的输出stream12似乎也能正常工作。

但是,一旦我按照给定的顺序对下列stream进行处理,

 0< stream0.txt 3< stream3.txt echo/ 

托pipe命令提示符实例立即closures。

当我改变stream的顺序,它再次正常工作:

 3< stream3.txt 0< stream0.txt echo/ 

那么为什么当我尝试inputredirect到stream03的顺序时, cmd实例意外终止?

我正在使用Windows 7(x64)。


当我尝试执行上述失败的inputredirect之前,我通过cmd打开一个新的命令提示符实例:

 cmd 0< stream0.txt 3< stream3.txt echo/ 

我可以阅读出现的错误消息 – 假设文本文件包含其基本名称后跟换行符:

 'stream3' is not recognised as an internal or external command, operable program or batch file. 

当我执行以下操作时,我可以看到两个文件实际上都被redirect了:

 cmd /V 0< stream0.txt 3< stream3.txt (set /P #="" & echo/!#!) cmd /V 0< stream0.txt 3< stream3.txt (<&3 set /P #="" & echo/!#!) 

各自输出:

 stream0 

和:

 stream3 

这到底是怎么回事?

注意 :这是执行重定向命令时cmd内部发生的简化。

让我们从指定的命令开始

 0< file1 3< file2 echo/ 

该命令被解析并在内存中创建所需的重定向的表示,某种表/列表将保存关于重定向的信息:哪个句柄被重定向,旧保存的句柄,重定向时句柄应该指向的位置, …

  Redirection requests ------------------------------- redirect saved redirectTo +--------+--------+------------ R1 | 0 file1 | R2 | 3 file2 

此时(解析命令后)没有流被改变。

还有一个系统表,用于处理每个文件描述符(在我们的例子中是cmd流)真正指向的位置。

  File descriptors ------------------ points to +----------------- 0 | stdin 1 | stdout 2 | stderr 3 | 4 | 

注意这不完全正确,底层结构稍微复杂一些,但这样看起来更容易

当要执行该命令时,将调用内部SetRedir函数。 它遍历以前的重定向请求表,保存现有的句柄并创建所需的新句柄。 初始状态是

  Redirection requests File descriptors ------------------------------- ------------------ redirect saved redirectTo points to +--------+--------+------------ +----------------- R1 | 0 file1 0 | stdin | 1 | stdout R2 | 3 file2 2 | stderr 3 | 4 | 

重定向请求表(R1)中的第一个元素被检索,重定向流0到file1的请求。 有必要保存当前的句柄,以便以后能够恢复它。 对于这个操作,使用_dup()函数。 它将使用最小的可用文件描述符(上表中的流3)为传递的文件描述符(我们的代码中的流0)创建一个别名。 保存操作和旧手柄关闭的情况是

  R1[saved] = _dup( R1[redirect] ); _close( R1[redirect] ); Redirection requests File descriptors ------------------------------- ------------------ redirect saved redirectTo points to +--------+--------+------------ +----------------- R1 | 0 3 file1 0 | ---\ | 1 | stdout | R2 | 3 file2 2 | stderr | 3 | stdin <<--/ 4 | 

保存后,通过打开请求的文件并关联文件描述符表中的打开文件句柄来完成重定向。 在这种情况下, _dup2()函数处理操作

  _dup2( CreateFile( R1[redirectTo] ), R1[redirect] ); Redirection requests File descriptors ------------------------------- ------------------ redirect saved redirectTo points to +--------+--------+------------ +----------------- R1 | 0 3 file1 0 | file1 <<--- | 1 | stdout R2 | 3 file2 2 | stderr 3 | stdin 4 | 

第一个重定向已经完成。 现在是时候和第二个做同样的操作了。 首先,使用_dup()函数保存旧的句柄。 这将把请求的文件描述符(3)与可用的最低描述符(4)

  R2[saved] = _dup( R2[redirect] ); _close( R2[redirect] ); Redirection requests File descriptors ------------------------------- ------------------ redirect saved redirectTo points to +--------+--------+------------ +----------------- R1 | 0 3 file1 0 | file1 | 1 | stdout R2 | 3 4 file2 2 | stderr 3 | ---\ 4 | stdin <<--/ 

重定向是通过打开输入文件并将其与文件描述符关联来完成的

 _dup2( CreateFile( R2[redirectTo] ), R2[redirect] ); Redirection requests File descriptors ------------------------------- ------------------ redirect saved redirectTo points to +--------+--------+------------ +----------------- R1 | 0 3 file1 0 | file1 | 1 | stdout R2 | 3 4 file2 2 | stderr 3 | file2 <<--- 4 | stdin 

重定向已经完成,命令执行时,流0重定向到file1 ,流3重定向到file2

一旦完成,现在是时候恢复过程。 ResetRedir()函数处理操作。 它再次使用_dup2()函数将保存的句柄传输到原始文件描述符。 这里出现的问题是保存的描述符被改变了

 _dup2( R1[saved], R1[redirect] ); R1[saved] = null; Redirection requests File descriptors ------------------------------- ------------------ redirect saved redirectTo points to +--------+--------+------------ +----------------- R1 | 0 file1 0 | file2 <<--\ | 1 | stdout | R2 | 3 4 file2 2 | stderr | 3 | ---/ 4 | stdin 

现在,第二次重定向也完成了相同的操作

 _dup2( R2[saved], R2[redirect] ); R2[saved] = null; Redirection requests File descriptors ------------------------------- ------------------ redirect saved redirectTo points to +--------+--------+------------ +----------------- R1 | 0 file1 0 | file2 | 1 | stdout R2 | 3 file2 2 | stderr 3 | stdin <<--\ 4 | ---/ 

一旦重定向被移除, &0句柄指向file2stdin流将被存储在&3 。 这可以作为测试

 @echo off setlocal enableextensions disabledelayedexpansion >file1 echo This is file 1 >file2 echo This is file 2 echo Test 1 - trying to read from stdin after redirection cmd /v /c"( 0< file1 3< file2 echo - test1 ) & set /p .=prompt & echo !.!" echo( echo( echo Test 2 - trying to read from stream 3 after redirection cmd /v /c"( 0< file1 3< file2 echo - test 2 ) & <&3 set /p .=prompt & echo !.!" 

这将会产生

 W:\>testRedirection.cmd Test 1 - trying to read from stdin after redirection - test1 prompt This is file 2 Test 2 - trying to read from stream 3 after redirection - test 2 prompt This is typed text This is typed text W:\> 

可以看出,在第一个测试中, set /pfile2读取,在第二个测试中,试图从&3读取stdin流。

看起来像一个错误,在命令完成之后流的恢复方式。 考虑下面的批处理文件:

 0< stream0.txt 3< stream3.txt findstr . findstr . <CON pause 

第一个findstr会按预期输出stream0.txt的内容。

第二个findstr会意外地输出stream3.txt的内容,表示流0意外地被重定向。

批处理文件完成后,运行它的命令shell也将退出,因为它在现在是标准输入流的文件上看到了文件的结尾。


根据MC ND的回答重试我的一个实验,现在我可以重现与流3以外的流相同的问题。考虑test7.cmd

 0< stream0.txt 4< stream3.txt findstr . findstr . :done <CON pause 

cmd /c test7cmd /c test7 3< other.txt不会出现意外的行为,而是以cmd /c "test7 3< other.txt"运行。 (这是一个facepalm的时刻;如果我仔细考虑过,我可能已经发现了这个,显然最初的重定向必须在运行批处理脚本的同一个命令shell的上下文中。

所以原因不是“重定向流3和流0”,而是“重定向第一个自由流和流0”。 🙂