Why can't I redirect to two input streams `0<` and `3<` in that order? Why can't I redirect to two input streams `0<` and `3<` in that order? windows windows

Why can't I redirect to two input streams `0<` and `3<` in that order?


note: This is a simplification of what happens inside cmd when the redirected command is executed.

Let's start with the indicated command

0< file1  3< file2 echo/

The command is parsed and a representation of the needed redirections is created in memory, some kind of table/list that will hold the information about the redirection: which handle is redirected, the old saved handle, to where the handle should point when redirected, ...

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

At this point (after parsing the command) no stream has been changed.

There is also a system table that handles where each of the file descriptors (in our case, the cmd streams) really points.

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

note This is not exactly true, the underlying structure is a little more complex, but that way it is easier to see how it works

When the command is going to be executed, the internal SetRedir function is called. It iterates over the previous redirection request table saving existing handles and creating required new ones. The initial state is

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

First element from redirection request table (R1) is retrieved, the requests to redirect stream 0 to file1. It is necessary to save the current handle to later be able to restore it. For this operation the _dup() function is used. It will create an alias for the passed file descriptor (stream 0 in our code), using the smallest available file descriptor (stream 3 in previous table). After save operation and old handle close the situation is

   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 |

Once saved, the redirection is completed by opening the requested file and associating open file handle in the file descriptors table. In this case the_dup2() function handles the operation

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

The first redirection has been done. It is time to do the same operation with the second one. First, save the old handle using the _dup() function. This will associate the requested file descriptor (3) with the lowest available descriptor (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 | stdoutR2 | 3        4        file2                  2 | stderr                                              3 |         ---\                                              4 | stdin  <<--/

The redirection is completed by opening the input file and associating it with the file descriptor

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

The redirection has been completed and the command is executed with the stream 0 redirected to file1 and the stream 3 redirected to file2.

Once done, it's time to revert the process. ResetRedir() function handles the operation. It uses again the _dup2() function to transfer the saved handle to the original file descriptor. Here the problem arises as the saved descriptor was changed

_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

Now, the same operation is done with the second redirection

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

Once the redirection has been removed the &0 handle points to file2 and the stdin stream is stored in &3. This can be tested as

@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 !.!"

That will generate

W:\>testRedirection.cmdTest 1 - trying to read from stdin after redirection- test1prompt This is file 2Test 2 - trying to read from stream 3 after redirection- test 2prompt This is typed textThis is typed textW:\>

It can be seen that in the first test, the set /p has read from file2, and in the second test, trying to read from &3 the stdin stream can be reached.


Looks like a bug with the way the streams are being restored after the command has finished. Consider the following batch file:

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

The first findstr will output the content of stream0.txt as expected.

The second findstr will unexpectedly output the content of stream3.txt, indicating that stream 0 has unexpectedly become redirected.

When the batch file finishes, the command shell running it will exit too, because it sees an end of file on what is now the standard input stream.


Retrying one of my experiments in the light of MC ND's answer, I can now reproduce the same issue with a stream other than stream 3. Consider test7.cmd:

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

Run as cmd /c test7 or as cmd /c test7 3< other.txt it does not exhibit the unexpected behaviour, but run as cmd /c "test7 3< other.txt" it does. (That was a facepalm moment; I could have discovered this yesterday if I'd thought about it more carefully. Obviously the initial redirection has to be in the context of same command shell that is running the batch script.)

So the cause isn't "redirecting stream 3 as well as stream 0" but "redirecting the first free stream as well as stream 0". :-)