为什么我不能按顺序重定向到两个输入流`0 <`和`3 <`?

asc*_*pfl 6 windows cmd io-redirection

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

3< stream3.txt 4< stream4.txt echo/
Run Code Online (Sandbox Code Playgroud)

类似的东西也可以使用更多的流和文件以及其他命令echo/,甚至是功能代码块.即使使用保留的输出流1,2似乎也能正常工作.

但是,只要我按照给定的顺序使用以下流执行此操作:

0< stream0.txt 3< stream3.txt echo/
Run Code Online (Sandbox Code Playgroud)

托管命令提示符实例立即关闭.

当我更改流的顺序时,它再次正常工作:

3< stream3.txt 0< stream0.txt echo/
Run Code Online (Sandbox Code Playgroud)

那么,cmd当我尝试输入 - 重定向到流0并3按此顺序时,为什么实例意外终止?

我使用的是Windows 7(x64).


当我cmd尝试执行上述失败的输入重定向之前,我打开一个新的命令提示符实例:

cmd
0< stream0.txt 3< stream3.txt echo/
Run Code Online (Sandbox Code Playgroud)

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

'stream3' is not recognised as an internal or external command, operable program or batch file.
Run Code Online (Sandbox Code Playgroud)

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

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

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

各自的输出:

stream0
Run Code Online (Sandbox Code Playgroud)

和:

stream3
Run Code Online (Sandbox Code Playgroud)

这到底是怎么回事?

MC *_* ND 6

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

让我们从指定的命令开始

0< file1  3< file2 echo/
Run Code Online (Sandbox Code Playgroud)

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

    Redirection requests
   ------------------------------- 
    redirect   saved   redirectTo
   +--------+--------+------------
R1 | 0                 file1
   |
R2 | 3                 file2
Run Code Online (Sandbox Code Playgroud)

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

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

    File descriptors
   ------------------  
    points to
   +----------------- 
 0 | stdin
 1 | stdout
 2 | stderr
 3 |
 4 |
Run Code Online (Sandbox Code Playgroud)

注意这并不完全正确,底层结构稍微复杂一些,但这样更容易了解它是如何工作的

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

    Redirection requests                         File descriptors
   -------------------------------              ------------------
    redirect   saved   redirectTo                points to
   +--------+--------+------------              +-----------------
R1 | 0                 file1                  0 | stdin
   |                                          1 | stdout
R2 | 3                 file2                  2 | stderr
                                              3 |
                                              4 |
Run Code Online (Sandbox Code Playgroud)

检索重定向请求表 (R1) 中的第一个元素,将流 0 重定向到文件 1 的请求。有必要保存当前句柄以便以后能够恢复它。对于此操作,_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 |
Run Code Online (Sandbox Code Playgroud)

保存后,通过打开请求的文件并在文件描述符表中关联打开的文件句柄来完成重定向。在这种情况下, _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 |
Run Code Online (Sandbox Code Playgroud)

第一次重定向已经完成。是时候对第二个进行相同的操作了。首先,使用该_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  <<--/
Run Code Online (Sandbox Code Playgroud)

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

_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
Run Code Online (Sandbox Code Playgroud)

重定向已完成,执行命令时流 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
Run Code Online (Sandbox Code Playgroud)

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

_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 |         ---/
Run Code Online (Sandbox Code Playgroud)

一旦重定向被移除,&0句柄指向file2并且stdin流存储在&3. 这可以测试为

0< file1  3< file2 echo/
Run Code Online (Sandbox Code Playgroud)

那会产生

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:\>
Run Code Online (Sandbox Code Playgroud)

由此可以看出,在第一次测试中,set /p已经从阅读file2,并在第二次测试中,试图读取&3该stdin流即可到达。

  • @aschipfl,输入和输出重定向在同一个函数内处理,在同一个循环内,它只考虑句柄替换。主要区别仅在于如何检索新句柄。在你的 `1&gt; stream1.txt 3&gt; stream3.txt echo/` 情况下,`cmd` 没有被挂起,只是 `stdout` 被发送到 `stream3.txt`。一般规则是定义从较高流到较低流的重定向(如果可能)。 (2认同)