Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 324
aschipfl Avatar asked Feb 21 '17 23:02

aschipfl


People also ask

What are the 3 standard streams in Linux can you redirect them?

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.

How standard streams can be redirected in Unix?

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.

How do you redirect the output of a command to a file without overwriting the contents of an existing file?

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.

How does input redirection work?

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.


1 Answers

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.

like image 178
MC ND Avatar answered Oct 18 '22 15:10

MC ND