When I try to redirect multiple files into multiple streams, like in the following example, everything works as expected:
3< stream3.txt 4< stream4.txt echo/
Something like that works also with more streams and files and other commands than echo/
or even a functional block of code. Even using the reserved output streams 1
and 2
seem to work properly.
However, as soon as I do that with the following streams in the given order:
0< stream0.txt 3< stream3.txt echo/
The hosting command prompt instance is immediately closed.
When I change the order of the streams, it works fine again:
3< stream3.txt 0< stream0.txt echo/
So why is the cmd
instance terminated unexpectedly when I try to input-redirect to stream 0
and 3
in that order?
I am using Windows 7 (x64).
When I open a new command prompt instance by cmd
before I try to executed the aforementioned failing input redirection:
cmd
0< stream0.txt 3< stream3.txt echo/
I can read the appearing error message -- supposing the text files contain their base name followed by a line-break:
'stream3' is not recognised as an internal or external command, operable program or batch file.
When I do the following, I can see that both files are actually redirected:
cmd /V
0< stream0.txt 3< stream3.txt (set /P #="" & echo/!#!)
cmd /V
0< stream0.txt 3< stream3.txt (<&3 set /P #="" & echo/!#!)
Respective output:
stream0
And:
stream3
What the hell is going on here?
There are 3 type of standard streams; standard input (stdin), standard output (stdout) and standard error (stderror). We'll go through what each term means by using the command cat as an example. in the terminal.
While the standard streams have default associations, they can be redirected. For example, standard input can be redirected to read from a file or the output of another program, while standard output or standard error can be redirected to write to a file or to send its output as input to another program.
The command >> file pattern redirects the standard output of a command to a file without overwriting the file's existing contents. This pair of commands first redirects the text inputted by the user through echo to a new file.
A program that reads input from the keyboard can also read input from a text file. This is called input redirection, and is a feature of the command line interface of most operating systems. This is text from the file.
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 | stdout
R2 | 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 | stdout
R2 | 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 | stdout
R2 | 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 | stdout
R2 | 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 | stdout
R2 | 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.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:\>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With