2017-02-21 24 views
6

Kiedy próbuję przekierować wielu plików w wielu strumieni, tak jak w poniższym przykładzie, wszystko działa zgodnie z oczekiwaniami:Dlaczego nie mogę przekierować do dwóch strumieni wejściowych `0 <` i `3 <` w tej kolejności?

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

Coś, który działa również z większą liczbą potoków i plików oraz innych poleceń niż echo/ lub nawet funkcjonalny blok kodu. Nawet używanie zastrzeżonych strumieni wyjściowych 1 i wydaje się działać poprawnie.

jednak tak szybko, jak to zrobić z następujących strumieni w podanej kolejności:

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

Komenda gospodarzem szybka instancji jest natychmiast zamknięte.

Kiedy zmienić kolejność strumieni, to znowu działa prawidłowo:

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

Więc dlaczego jest instancja cmd niespodziewanie zakończyła się przy próbie wejścia przekierowanie strumienia 0 i 3 w tej kolejności?

Używam systemu Windows 7 (x 64).


Kiedy otworzyć nową instancję wiersza polecenia przez cmd przed próbuję wykonany ww uszkodzoną przekierowanie wejścia:

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

mogę przeczytać pojawiające komunikat o błędzie - Zakładając, że pliki tekstowe zawierają ich Nazwa zasady lub następnie końca linii:

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

Kiedy wykonać następujące czynności, widzę, że oba pliki są faktycznie przekierowane:

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

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

wyjściową odpowiednich:

stream0 

I:

stream3 

Co się do cholery dzieje tutaj ?

Odpowiedz

4

uwaga: Jest to uproszczenie, co dzieje się wewnątrz cmd gdy przekierowany komenda jest wykonywana.

Zacznijmy wskazanego polecenia

0< file1 3< file2 echo/ 

Polecenie jest analizowany i reprezentacji potrzebnych przekierowań jest tworzony w pamięci jakieś tabeli/listy, które przechowuje informacje o przekierowanie: który obsługiwać jest przekierowywany, stary zapisany uchwyt, gdzie uchwyt powinien wskazywać kiedy przekierowany ...

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

w tym momencie (po parsowania polecenia) nie strumień został zmieniony.

Istnieje również tabela systemowa, która obsługuje, gdzie każdy z deskryptorów plików (w naszym przypadku, strumienie cmd) naprawdę wskazuje.

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

uwaga To nie jest do końca prawda, podstawowa struktura jest nieco bardziej skomplikowane, ale w ten sposób łatwiej jest zobaczyć, jak to działa

Gdy komenda zostanie wykonana , wywoływana jest wewnętrzna funkcja SetRedir. Powtarza on poprzednią tabelę żądań przekierowania, zapisując istniejące uchwyty i tworząc wymagane nowe. Początkowy stan jest

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

pierwszy element przekierowanie żądania tabeli (R1) jest pobierany, żądania aby przekierowanie strumienia 0 do pliku1. Konieczne jest zapisanie bieżącego uchwytu, aby później móc go przywrócić. Do tej operacji używana jest funkcja _dup(). Tworzy alias dla przesłanego deskryptora pliku (strumień 0 w naszym kodzie), używając najmniejszego dostępnego deskryptora pliku (strumień 3 z poprzedniej tabeli). Po operacji zapisu i stare klamki zamknąć sytuacja jest

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 | 

Po zapisaniu przekierowanie jest zakończona poprzez otwarcie żądany plik i skojarzenie uchwyt pliku otwartego w tabeli deskryptorów.W tym przypadku funkcja _dup2() obsługuje operację

_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 | 

Pierwszy przekierowanie zostało zrobione. Nadszedł czas, aby wykonać tę samą operację z drugą, . Najpierw zapisz stary uchwyt za pomocą funkcji _dup(). To powiązanie żądany deskryptora (3) o najniższym dostępnym deskryptora (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 <<--/ 

Przekierowanie uzupełnia otwarcia pliku wejściowego i kojarzenia go z deskryptora

_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 

przekierowania zostało zakończone i polecenie jest wykonywane ze strumieniem 0 przekierowanym do file1, a strumień 3 przekierowany do file2.

Po zakończeniu, nadszedł czas, aby przywrócić proces. Funkcja ResetRedir() obsługuje operację. Ponownie używa funkcji _dup2(), aby przenieść zapisany uchwyt do oryginalnego deskryptora pliku. Tutaj powstaje problem jak zapisane deskryptor zmieniono

_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 

Teraz ta sama operacja jest wykonywana z drugiego przekierowania

_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 |   ---/ 

Gdy przekierowanie zostało usunięte &0 punkty uchwytu do file2 i stdin strumień jest przechowywany w &3. To mogą być testowane jako

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

To wygeneruje

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:\> 

Można zauważyć, że w pierwszym teście, set /p zapoznał się z file2, aw drugim teście, próbując odczytać z &3 można uzyskać strumień stdin.

+0

Wow, doskonała odpowiedź, +1 !! To doprowadziło mnie do innego scenariusza, który się nie powiódł: '3 stream1.txt 3> stream3.txt echo /' ('cmd' zawiesza się dopóki nie wejdę w' exit') lub '2> stream2.txt 3> stream3.txt echo/'(przekierowany uchwyt' 2' nie jest poprawnie zamknięty); wygląda na to, że wewnętrzny proces jest taki sam dla przekierowania wejścia i wyjścia ... – aschipfl

+0

Więc myślę, że mógłbym powiedzieć z reguły, że: * »Po przekierowaniu predefiniowanego uchwytu' 0'/'1' /' 2', nie używaj następnej wolnej klamki (ani dla wejścia, ani dla przekierowania wyjścia) dla tego samego wiersza poleceń/bloku. «* – aschipfl

+2

@aschipfl, przekierowanie wejścia i wyjścia jest obsługiwane wewnątrz tej samej funkcji, wewnątrz tej samej pętli, to tylko odpowiada za wymiana uchwytu. Główną różnicą jest tylko sposób pobrania nowego uchwytu. W twoim strumieniu '1> stream1.txt 3> stream3.txt echo/'case,' cmd' nie jest zawieszony, po prostu 'stdout' jest wysyłany do' stream3.txt'. Ogólną zasadą jest definiowanie przekierowań (o ile to możliwe) z wyższych do niższych strumieni. –

3

Wygląda jak błąd w sposobie przywracania strumieni po wykonaniu polecenia. Rozważmy następujący plik wsadowy:

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

findstr . 

<CON pause 

pierwsze wyjście findstr będzie zawartość stream0.txt jak oczekiwano.

Drugi findstr nieoczekiwanie wyświetli zawartość stream3.txt, wskazując, że strumień 0 niespodziewanie został przekierowany.

Po zakończeniu pliku wsadowego, działająca z niego powłoka poleceń również zakończy pracę, ponieważ widzi koniec pliku na tym, co jest obecnie standardowym strumieniem wejściowym.


Ponowna próba jeden z moich doświadczeń w świetle MC ND's answer, mogę teraz odtworzyć ten sam problem ze strumieniem innych niż strumień 3. Rozważmy test7.cmd:

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

findstr . 

:done 
<CON pause 

Uruchom jako cmd /c test7 lub cmd /c test7 3< other.txt go nie wykazuje nieoczekiwanego zachowania, ale działa tak, jak to robi. (To była chwila facepalm, mogłem to odkryć wczoraj, gdybym myślał o tym bardziej uważnie .. Oczywiście początkowe przekierowanie musi być w kontekście tej samej powłoki poleceń, która uruchamia skrypt wsadowy.)

Tak przyczyną nie jest "przekierowanie strumienia 3, a także strumienia 0", ale "przekierowanie pierwszego wolnego strumienia, jak również strumienia 0". :-)

+0

Przyjemne znalezisko, +1! Pytanie brzmi: dlaczego tak się dzieje tylko w przypadku strumienia 3? wydaje mi się, że strumień 3 nie jest całkowicie niezdefiniowany lub nie jest używany jako [udokumentowany] (https://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/redirection.mspx); może 'cmd' używa go wewnętrznie do czegoś ... – aschipfl

+0

To była moja myśl, ale żadna z innych wariacji, które próbowałem, nie znalazła niczego. Możliwe, że uda się to wykonać za pomocą inżynierii odwrotnej 'cmd.exe', ale nie mam takiego czasu. :-) –

Powiązane problemy