Jaka jest różnica między „plikiem kota | ./binary ”i„ ./binary <plik ”?


102

Mam plik binarny (którego nie mogę modyfikować) i mogę:

./binary < file

Mogę też zrobić:

./binary << EOF
> "line 1 of file"
> "line 2 of file"
...
> "last line of file"
> EOF

Ale

cat file | ./binary

daje mi błąd. Nie wiem, dlaczego to nie działa z rurą. We wszystkich 3 przypadkach zawartość pliku jest podawana na standardowe wejście binarne (na różne sposoby):

  1. bash czyta plik i przekazuje go do standardowego pliku binarnego
  2. bash czyta wiersze ze standardowego wejścia (do EOF) i przekazuje je do standardowego pliku binarnego
  3. cat czyta i umieszcza wiersze pliku na standardowe wyjście, bash przekierowuje je na standardowe wejście binarne

Binarny nie powinien zauważyć różnicy między tymi 3, o ile je rozumiem. Czy ktoś może wyjaśnić, dlaczego trzeci przypadek nie działa?

BTW: Błąd podany przez plik binarny to:

20170116 / 125624.689 - U3000011 Nie można odczytać pliku skryptu „”, kod błędu „14”.

Ale moje główne pytanie brzmi: jaka jest różnica dla każdego programu z tymi 3 opcjami.

Oto kilka dalszych szczegółów: spróbowałem ponownie z strace i rzeczywiście wystąpiły błędy ESPIPE (nielegalne wyszukiwanie) z lseek, a następnie EFAULT (zły adres) z odczytu tuż przed komunikatem o błędzie.

Plik binarny, który próbowałem kontrolować za pomocą skryptu ruby ​​(bez użycia plików tymczasowych), jest częścią callapi z Automic (UC4) .


25
Fajnie, w twoim pliku binarnym znajduje się detektor UUOC . Chcę to.
xhienne

4
Co to jest za system operacyjny (więc możemy powiedzieć, co to 14, jeśli ma to być errno)?
Stéphane Chazelas

6
Nawet jeśli jest to możliwe, aby program mógł reagować w ten sposób, że będzie to stangely buggy, który zrobił. Każdy nie zwariowany program, który w ogóle oczekuje jakiegokolwiek wejścia od stdin, musi działać, gdy stdin jest tty, a jeśli może działać zarówno z tty, jak i plikiem, nie ma powodu, aby nie obsługiwać także potoków. Prawdopodobnie autor programu miał tymczasowy krwotok i chociaż wszystko, co isatty()zwróci fałsz, będzie widoczne lub możliwe do zastosowania w pliku ...
Henning Makholm

9
Kod błędu 14 oznacza EFAULT. Odczyt, który występuje, jeśli zadeklarowany bufor jest nieprawidłowy. Zrobiłbym śledzenie programu, ale podejrzewam, że szuka on końca pliku, aby uzyskać rozmiar bufora do odczytu danych, źle radzi sobie z tym, że funkcja wyszukiwania nie działa i próbuje przydzielić rozmiar ujemny (nie obsługuje złego malloc) . Przekazywanie bufora w celu odczytania, które błędy w danym buforze są nieprawidłowe.
Matthew Ife

3
@xhienne Nie, ma w sobie prewencję cat. Wygląda na to, że nie można go użyć do połączenia dwóch plików, podobnie jak zamierzone użycie.
jpmc26

Odpowiedzi:


150

W

./binary < file

binaryStdin to plik otwarty w trybie tylko do odczytu. Zauważ, że w bashogóle nie odczytuje pliku, po prostu otwiera go do odczytu na deskryptorze pliku 0 (stdin) procesu, w którym wykonuje binary.

W:

./binary << EOF
test
EOF

W zależności od powłoki, binarystdin będzie albo usuniętym plikiem tymczasowym (AT&T ksh, zsh, bash ...), który zawiera, test\njak tam umieszczony przez powłokę lub koniec odczytu potoku ( dash, yash; i powłoka zapisuje test\nrównolegle na drugim końcu rury). W twoim przypadku, jeśli używasz bash, byłby to plik tymczasowy.

W:

cat file | ./binary

W zależności od powłoki, binarystdin będzie albo końcem odczytu rury, albo jednym końcem pary gniazd, w którym kierunek zapisu został wyłączony (ksh93) i catzapisuje zawartość filena drugim końcu.

Kiedy stdin jest zwykłym plikiem (tymczasowym lub nie), można go zobaczyć. binarymoże przejść do początku lub końca, przewinąć do tyłu itp. Może także ioctl()sodwzorować , wykonać kilka takich jak FIEMAP / FIBMAP (jeśli użyje <>zamiast tego <, może obciąć / dziurkować w nim itp.).

z drugiej strony pary rur i gniazd są środkami komunikacji międzyprocesowej, binaryoprócz readdanych niewiele można zrobić (chociaż są też pewne operacje, takie jak niektóre specyficzne dla rur, ioctl()które można by na nich wykonać, a nie na zwykłych plikach) .

Większość czasu, to brakujące umiejętność seek, która powoduje, aplikacje na niepowodzenie / skarżą się podczas pracy z rurami, ale może to być jakikolwiek inny wywołań systemowych, które obowiązują na zwykłych plików, ale nie na różnych typach plików (jak mmap(), ftruncate(), fallocate()) . W Linuksie istnieje również duża różnica w zachowaniu, gdy otwierasz, /dev/stdingdy fd 0 jest w potoku lub w zwykłym pliku.

Istnieje wiele poleceń, które obecnie nie mogą zajmować się tylko możliwy do przeszukania plików, ale kiedy to przypadek, to nie jest na ogół za pliki otwarte na ich stdin.

$ unzip -l file.zip
Archive:  file.zip
  Length      Date    Time    Name
---------  ---------- -----   ----
       11  2016-12-21 14:43   file
---------                     -------
       11                     1 file
$ unzip -l <(cat file.zip)
     # more or less the same as cat file.zip | unzip -l /dev/stdin
Archive:  /proc/self/fd/11
  End-of-central-directory signature not found.  Either this file is not
  a zipfile, or it constitutes one disk of a multi-part archive.  In the
  latter case the central directory and zipfile comment will be found on
  the last disk(s) of this archive.
unzip:  cannot find zipfile directory in one of /proc/self/fd/11 or
        /proc/self/fd/11.zip, and cannot find /proc/self/fd/11.ZIP, period.

unzipmusi odczytać indeks zapisany na końcu pliku, a następnie szukać w pliku, aby odczytać członków archiwum. Ale tutaj plik (zwykły w pierwszym przypadku, potok w drugim) jest podawany jako argument ścieżki do unzipi unzipsam się otwiera (zwykle na fd innym niż 0) zamiast dziedziczenia fd już otwartego przez rodzica. Nie odczytuje plików zip ze standardowego wejścia. Stdin jest najczęściej używany do interakcji użytkownika.

Jeśli uruchomisz to binarybez przekierowania po zachęcie interaktywnej powłoki działającej w emulatorze terminali, binarystdin zostanie odziedziczony po nadrzędnej powłoce, która sama odziedziczy go po nadrzędnej emulatorze terminali i będzie Urządzenie pty otwarte w trybie odczytu + zapisu (coś w stylu /dev/pts/n).

Te urządzenia też nie są widoczne. Jeśli więc binarydziała poprawnie podczas pobierania danych z terminala, być może problem nie polega na szukaniu.

Jeśli ta 14 ma być błędem (kod błędu ustawiony przez nieudane wywołania systemowe), to w większości systemów byłoby to EFAULT( Zły adres ). read()Wywołanie systemowe nie powiedzie się z tego błędu, jeśli poprosił, aby przeczytać na adres pamięci, który nie jest zapisywalny. Byłoby to niezależne od tego, czy fd odczytuje dane z punktów do potoku lub zwykłego pliku i ogólnie wskazuje na błąd 1 .

binaryprawdopodobnie określa typ pliku otwartego na stdin (with fstat()) i napotyka błąd, gdy nie jest to zwykły plik ani urządzenie tty.

Trudno powiedzieć, nie wiedząc więcej o aplikacji. Uruchomienie go pod strace(lub truss/ lub tuscodpowiednikiem w twoim systemie) może pomóc nam zobaczyć, co to jest wywołanie systemowe, jeśli coś tutaj nie działa.


1 Scenariusz przewidziany przez Matthew Ife w komentarzu do twojego pytania brzmi tutaj bardzo realistycznie. Cytując go:

Podejrzewam, że szuka końca pliku, aby uzyskać rozmiar bufora do odczytu danych, źle radzi sobie z tym, że funkcja seek nie działa i próbuje przydzielić rozmiar ujemny (nie obsługuje złego malloc). Przekazywanie bufora w celu odczytania, które błędy w danym buforze są nieprawidłowe.


14
Bardzo interesujące ... po raz pierwszy słyszałem, że przekierowane standardowe wejście w stylu ./binary < filejest widoczne!
David Z

2
@DavidZ to plik, który został edytowany openi zachowuje się tak samo jak każdy edytowany plik open. Zdarza się, że został odziedziczony po procesie nadrzędnym, ale to nie jest tak rzadkie.
hobbs

3
Jeśli system zawiera strace lub podobne narzędzie, można go użyć do sprawdzenia, które wywołanie systemowe nie działa.
pabouk

2
„Może także obcinać, mmapować, dziurkować itp.” - Więc nie. Plik jest otwarty w trybie tylko do odczytu. Aby to zrobić, program musiałby go otworzyć w trybie zapisu. Ale nie można go otworzyć w trybie zapisu, ponieważ nie ma interfejsu do robienia tego bezpośrednio, ani też nie ma interfejsu do znalezienia „pozycji” katalogu, która odpowiada otwartemu plikowi (co jeśli dwa takie dentrie czy zero?) . Musiałby zarejestrować plik, a następnie przeskanować system plików w poszukiwaniu obiektu o tym samym numerze i-węzła. To byłoby niezwykle powolne.
Kevin

1
@ StéphaneChazelas: och, open("/proc/self/fd/0", O_RDWR)działa, nawet na usuniętych plikach. Silly me: P. echo foo>foo; (sleep 0.5; ll -L /proc/self/fd/0; strace ./a.out; ll -L /proc/self/fd/0) < foo & sleep 0.1 && rm foorozłącza się fooprzed uruchomieniem a.out z jego stdin przekierowanym z foo.
Peter Cordes

46

Oto prosty przykład program, który ilustruje odpowiedź Stéphane Chazelas' stosując lseek(2)na wejściu:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main(void)
{
    int c;
    off_t off;
    off = lseek(0, 10, SEEK_SET);
    if (off == -1)
    {
        perror("Error");
        return -1;
    }
    c = getchar();
    printf("%c\n", c);
}

Testowanie:

$ make seek
cc     seek.c   -o seek
$ cat foo
abcdefghijklmnopqrstuwxyz
$ ./seek < foo
k
$ ./seek <<EOF
> abcdefghijklmnopqrstuvwxyz
> EOF
k
$ cat foo | ./seek
Error: Illegal seek

Rury nie są widoczne, i to jest jedno miejsce, w którym program może narzekać na rury.


21

Fajka i przekierowanie to różne zwierzęta, że ​​tak powiem. Kiedy używasz here-docprzekierowania ( <<) lub przekierowania stdin, < tekst nie wychodzi z tonu - trafia on do deskryptora pliku (lub pliku tymczasowego, jeśli chcesz), i tam właśnie wskaże stdin pliku binarnego.

Oto fragment bash'skodu źródłowego, plik redir.c (wersja 4.3):

/* Create a temporary file holding the text of the here document pointed to
   by REDIRECTEE, and return a file descriptor open for reading to the temp
   file.  Return -1 on any error, and make sure errno is set appropriately. */
static int
here_document_to_fd (redirectee, ri)

Ponieważ przekierowanie można zasadniczo traktować jako pliki, pliki binarne mogą nawigować po nich lub seek()łatwo przechodzić przez plik, przechodząc do dowolnego bajtu pliku.

Rurociągi, ponieważ są buforami 64 KiB (przynajmniej w Linuksie) z zapisami 4096 bajtów lub mniejszymi gwarantowanymi jako atomowe, nie są widoczne, tzn. Nie można swobodnie nimi nawigować - tylko czytać sekwencyjnie. Kiedyś zaimplementowałem tailpolecenie w Pythonie. Po przekierowaniu można znaleźć 29 milionów wierszy tekstu w mikrosekundach, ale w przypadku przekierowania catnic nie da się zrobić - więc wszystko należy odczytać sekwencyjnie.

Inną możliwością jest to, że plik binarny może chcieć specjalnie otworzyć plik i nie chce otrzymywać danych wejściowych z potoku. Zwykle odbywa się to poprzez fstat()wywołanie systemowe i sprawdzenie, czy dane wejściowe pochodzą z określonego S_ISFIFOtypu pliku (co oznacza potok / nazwany potok).

Twój konkretny plik binarny, ponieważ nie wiemy, co to jest, prawdopodobnie próbuje szukać, ale nie może szukać rur. Zaleca się zapoznanie się z jego dokumentacją, aby dowiedzieć się, co dokładnie oznacza kod błędu 14.

UWAGA : Niektóre powłoki, takie jak dash (Debian Almquist Shell, domyślnie /bin/shw Ubuntu) implementują here-docprzekierowanie z potokami wewnętrznie , dlatego mogą nie być widoczne. Punkt pozostaje ten sam - potoki są sekwencyjne i nie można w nich łatwo nawigować, a próby tego skutkują błędami.


Odpowiedź Stephane'a mówi, że tu-docs można zaimplementować za pomocą potoków i że niektóre popularne powłoki takie jak dashto robią. Ta odpowiedź wyjaśnia obserwowane zachowanie podczas bash, ale najwyraźniej takie zachowanie nie jest gwarantowane w innych powłokach.
Peter Cordes

@PeterCordes to absolutnie tak, a ja właśnie to zweryfikowałem dashw moim systemie. Nie wiedziałem o tym wcześniej. Dzięki za zwrócenie uwagi
Sergiy Kolodyazhnyy,

Kolejny komentarz: użyjesz fstat()standardowego wejścia, aby sprawdzić, czy jest to potok. statprzyjmuje nazwę ścieżki. Ale tak naprawdę, sama próba lseekjest prawdopodobnie najbardziej rozsądnym sposobem ustalenia, czy fd można zobaczyć po tym, jak już jest otwarty.
Peter Cordes,

5

Główną różnicą jest obsługa błędów.

W następującym przypadku błąd jest zgłaszany

$ /bin/cat < z.txt
-bash: z.txt: No such file or directory
$ echo $?
1

W następującym przypadku błąd nie jest zgłaszany.

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0

Dzięki bash nadal możesz używać PIPESTATUS:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo ${PIPESTATUS[0]}
1

Ale jest dostępny tylko natychmiast po wykonaniu polecenia:

$ cat z.txt | /bin/cat
cat: z.txt: No such file or directory
$ echo $?
0
$ echo ${PIPESTATUS[0]}
0
# oops !

Jest jeszcze jedna różnica, kiedy używamy funkcji powłoki zamiast plików binarnych. W bashfunkcji, które są częścią potoku są wykonywane w podpowłokach (z wyjątkiem ostatniego komponentu potoku, jeśli lastpipeopcja jest włączona i bashnie jest interaktywna), więc zmiana zmiennych nie ma wpływu na powłokę nadrzędną:

$ a=a
$ b=b
$ x(){ a=x;}
$ y(){ b=y;}

$ echo $a $b
a b

$ x | y
$ echo $a $b
a b

$ cat t.txt | y
$ echo $a $b
a b

$ x | cat
$ echo $a $b
a b

$ x < t.txt
$ y < t.txt
$ echo $a $b
x y

4
Więc pokazujesz, że obsługa błędów >odbywa się za pomocą powłoki, ale w potoku jest to wykonywane przez polecenie, które tworzy tekst. DOBRZE. Ale w tym konkretnym pytaniu OP używa istniejącego pliku, więc nie o to chodzi, a błąd jest generowany przez plik binarny.
Sergiy Kolodyazhnyy

1
Chociaż w większości nie ma to sensu, ta odpowiedź ma pewne znaczenie dla tego pytania i odpowiedzi w ogólnym przypadku i jest w większości poprawna, więc nie sądzę, że zasługuje na te negatywne opinie.
Stéphane Chazelas

@Serg: Gdy używasz powłoki jako wiersza poleceń, nie jest to ważne. Ale w skryptach obsługa błędów może być bardzo ważna.
Vouze
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.