2017-02-21 43 views
6

При попытке перенаправить несколько файлов в несколько потоков, как в следующем примере, все работает, как ожидалось:Почему я не могу перенаправить на два входных потока `0 <` и `3 <` в этом порядке?

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

Нечто подобное также работает с большим количеством потоков и файлов и других команд, чем echo/ или даже функциональная блок кода. Даже с использованием зарезервированных выходных потоков 1 и 2, похоже, работают правильно.

Однако, как только я сделать это с помощью следующих потоков в указанном порядке:

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

Хостинг командной строки экземпляр немедленно закрывается.

Когда я изменить порядок потоков, он отлично снова работает:

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

Так почему экземпляр cmd неожиданно прерван при попытке ввода-редирект на поток 0 и 3 в таком порядке?

Я использую Windows 7 (x64).


Когда я открываю новый командную строку экземпляра, cmd, прежде чем я пытаюсь выполнил вышеупомянутое неисправное перенаправление ввода:

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

Я могу прочитать сообщение об ошибке, появляющемся - допускающее текстовые файлы содержат их Базовое имя следует разрыв строки:

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

Когда я делаю следующее, я могу видеть, что оба файла на самом деле перенаправлены:

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

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

соотвeтствующeго выхода:

stream0 

И:

stream3 

Что, черт возьми, здесь происходит ?

ответ

4

примечание: Это упрощение, что происходит внутри 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 до файла1. Необходимо сохранить текущий дескриптор, чтобы впоследствии восстановить его. Для этой операции используется функция _dup(). Он создаст псевдоним для переданного дескриптора файла (поток 0 в нашем коде), используя наименьший доступный дескриптор файла (поток 3 в предыдущей таблице). После операции сохранения и старые ручки близко ситуация

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 ручки указывает на file2 и stdin поток хранится в &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 /p прочитал от file2, и во втором тесте, пытаясь прочитать от &3 поток stdin может быть достигнут.

+0

Ничего себе, отличный ответ, +1 !! Это привело меня к другому сценарию, который терпит неудачу: '3 stream1.txt 3> stream3.txt echo /' ('cmd' зависает, пока я не войду в' exit'), или '2> stream2.txt 3> stream3.txt echo/'(перенаправленный дескриптор' 2', похоже, не закрывается должным образом); похоже, что внутренний процесс одинаковый как для перенаправления ввода, так и вывода ... – aschipfl

+0

Итак, я думаю, что могу сказать, что: * »После перенаправления предопределенного дескриптора' 0'/'1' /' 2', не используйте следующую свободную рукоятку (ни для ввода, ни для перенаправления вывода) для той же командной строки/блока. «* – aschipfl

+2

@aschipfl, перенаправление ввода и вывода обрабатывается внутри одной и той же функции внутри одного и того же цикла, это просто учет замените ручку. Основное отличие заключается только в том, как извлекается новый дескриптор. В вашем '1> stream1.txt 3> stream3.txt echo/'case,' cmd' не вешается, просто 'stdout' отправляется в' stream3.txt'. Общее правило заключается в определении перенаправления (когда это возможно) от более высоких к более низким потокам. –

3

Похож на ошибку с тем, как потоки восстанавливаются после завершения команды. Рассмотрим следующий пакетный файл:

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

findstr . 

<CON pause 

Первый выход findstr воли содержание stream0.txt, как ожидалось.

Второй findstr неожиданно выводит содержимое stream3.txt, указывая, что поток 0 неожиданно стал перенаправлен.

Когда командный файл заканчивается, командная оболочка, запускающая его, тоже выйдет из-за того, что видит конец файла в том, что теперь является стандартным входным потоком.


Повторная один из моих экспериментов в свете MC ND's answer, теперь я могу воспроизвести ту же самую проблему с потоком, кроме потока 3. Рассмотрим test7.cmd:

0< stream0.txt 4< stream3.txt findstr . 

findstr . 

:done 
<CON pause 

Запуск cmd /c test7 или cmd /c test7 3< other.txt его не проявляет неожиданного поведения, но работает как cmd /c "test7 3< other.txt". (Это был момент facepalm, я мог бы обнаружить это вчера, если бы я подумал об этом более тщательно. Очевидно, что первоначальное перенаправление должно быть в контексте той же командной оболочки, которая запускает пакетный скрипт.)

So причина не «перенаправляет поток 3, а также поток 0», а «перенаправляет первый свободный поток, а также поток 0». :-)

+0

Приятное обнаружение, +1! Вопрос для меня: почему это происходит только с потоком 3? мне кажется, что поток 3 не является полностью неопределенным или неиспользуемым как [документально] (https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/redirection.mspx); возможно, 'cmd' использует его внутренне для чего-то ... – aschipfl

+0

Это была моя мысль, но ни одна из других вариаций, которые я пробовал, ни при чем. Возможно, это можно будет обработать с помощью обратного инжиниринга 'cmd.exe', но у меня нет такого времени. :-) –