Was ist der Unterschied zwischen "cat file | ./binary ”und“ ./binary <file ”?

102

Ich habe eine Binärdatei (die ich nicht ändern kann) und kann Folgendes:

./binary < file

Ich kann auch:

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

Aber

cat file | ./binary

gibt mir einen fehler. Ich weiß nicht, warum es mit einer Pfeife nicht funktioniert. In allen drei Fällen wird der Inhalt der Datei auf unterschiedliche Weise an die Standardeingabe von binary übergeben :

  1. bash liest die Datei und gibt sie an stdin of binary weiter
  2. bash liest Zeilen von stdin (bis EOF) und gibt sie an stdin of binary weiter
  3. cat liest und setzt die Dateizeilen auf stdout, bash leitet sie auf stdin von binary um

Die Binärdatei sollte den Unterschied zwischen diesen 3 nicht bemerken, soweit ich es verstanden habe. Kann jemand erklären, warum der 3. Fall nicht funktioniert?

Übrigens: Der durch die Binärdatei gegebene Fehler ist:

20170116 / 125624.689 - U3000011 Skriptdatei '', Fehlercode '14' konnte nicht gelesen werden.

Aber meine Hauptfrage ist, wie gibt es einen Unterschied für jedes Programm mit diesen 3 Optionen.

Hier sind einige weitere Details: Ich habe es erneut mit Strace versucht und es gab tatsächlich einige Fehler ESPIPE (Illegal seek) von lseek gefolgt von EFAULT (Bad address) von read direkt vor der Fehlermeldung.

Die Binärdatei, die ich mit einem Ruby-Skript zu steuern versuchte (ohne temporäre Dateien zu verwenden), ist Teil der Callapi von Automic (UC4) .

Boris
quelle
25
Cool, es gibt einen UUOC- Detektor in Ihrer Binärdatei. Ich will es.
Xhienne
4
Was für ein Betriebssystem ist das (also können wir sagen, was 14 ist, wenn es ein Fehler sein soll)?
Stéphane Chazelas
6
Obwohl es möglich ist , dass ein Programm auf diese Weise reagiert, wäre es ein seltsamer Fehler. Jedes nicht verrückte Programm, das eine Eingabe von stdin erwartet, muss funktionieren, wenn stdin ein tty ist. Wenn es sowohl mit tty als auch mit einer Datei funktionieren kann, gibt es kaum einen Grund, Pipes nicht zu unterstützen. Wahrscheinlich hatte der Autor des Programms eine vorübergehende Blutung und obwohl alles, was isatty()falsch ist, eine durchsuchbare oder abbildbare Datei ist ...
Henning Makholm
9
Fehlercode 14 steht für EFAULT. Bei einem Lesevorgang, der auftritt, wenn der von Ihnen deklarierte Puffer ungültig ist. Ich würde das Programm stracen, aber ich vermute, es versucht bis zum Ende der Datei, eine Puffergröße zum Lesen der Daten zu erhalten, die Tatsache schlecht zu handhaben, dass das Suchen nicht funktioniert, und zu versuchen, eine negative Größe zuzuweisen (kein schlechtes Malloc zu handhaben) . Das Übergeben des Puffers, um zu lesen, welche Fehler im Puffer vorliegen, ist ungültig.
Matthew Ife
3
@ xhienne Nein, es hat einen catPreventer. Es sieht so aus, als könnten Sie damit nicht zwei Dateien kombinieren, wie es die beabsichtigte Verwendung ist.
jpmc26

Antworten:

150

Im

./binary < file

binary's stdin ist die Datei, die im schreibgeschützten Modus geöffnet ist. Beachten Sie, dass bashdie Datei überhaupt nicht gelesen wird, sondern nur zum Lesen im Dateideskriptor 0 (stdin) des Prozesses geöffnet wird, binaryin dem sie ausgeführt wird .

Im:

./binary << EOF
test
EOF

Abhängig von der Shell ist binarystdin entweder eine gelöschte temporäre Datei (AT & T ksh, zsh, bash ...), die test\ndas von der Shell gesetzte Ende einer Pipe enthält , oder das Leseende einer Pipe ( dash, yash; und die Shell schreibt test\nparallel am anderen Ende der Leitung). In Ihrem Fall bashwäre dies eine temporäre Datei.

Im:

cat file | ./binary

Abhängig von der Shell ist binarystdin entweder das Leseende einer Pipe oder ein Ende eines Socket-Paares, bei dem die Schreibrichtung heruntergefahren wurde (ksh93) und catder Inhalt von fileam anderen Ende geschrieben wird.

Wenn stdin eine reguläre Datei ist (temporär oder nicht), kann danach gesucht werden. binarykann an den Anfang oder das Ende gehen, zurückspulen usw. Es kann es auch mappen, etwas ioctl()swie FIEMAP / FIBMAP tun (wenn es <>anstelle von verwendet wird <, könnte es Löcher darin abschneiden / lochen usw.).

Pipes und Socket-Paare hingegen sind ein Kommunikationsmittel zwischen Prozessen. binaryNeben readden Daten gibt es nicht viel zu tun (obwohl es auch einige Operationen gibt, wie z. B. einige Pipe-spezifische Operationen ioctl(), die für sie und nicht für reguläre Dateien ausgeführt werden könnten). .

Die meisten der Zeit, es ist die fehlende Möglichkeit, seekdie Anwendungen scheitern / beschweren , wenn die Arbeit mit Rohren verursacht, aber es könnte eine der anderen Systemaufrufe, die auf reguläre Dateien gültig sind , aber nicht auf verschiedene Arten von Dateien (wie mmap(), ftruncate(), fallocate()) . Unter Linux gibt es auch einen großen Unterschied im Verhalten, wenn Sie öffnen, /dev/stdinwährend sich der fd 0 in einer Pipe oder in einer regulären Datei befindet.

Es gibt viele Befehle, die sich nur mit durchsuchbaren Dateien befassen können, aber wenn dies der Fall ist, gilt dies im Allgemeinen nicht für die Dateien, die im Standardverzeichnis geöffnet sind.

$ 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.

unzipmuss den Index lesen, der am Ende der Datei gespeichert ist, und dann in der Datei nach den Archivmitgliedern suchen. Aber hier wird die Datei (regulär im ersten Fall, Pipe im zweiten Fall) als Pfadargument für angegeben unzipund unzipöffnet sie selbst (normalerweise auf fd ungleich 0), anstatt ein fd zu erben, das bereits vom übergeordneten Element geöffnet wurde. Es liest keine zip-Dateien von seinem Standard. stdin wird hauptsächlich für die Benutzerinteraktion verwendet.

Wenn Sie binaryIhre eigene Shell ohne Umleitung ausführen, binarywährend eine interaktive Shell in einem Terminalemulator ausgeführt wird, wird die Standard-ID von der übergeordneten Shell geerbt, die sie selbst von der übergeordneten Shell, dem Terminalemulator, geerbt hat pty-Gerät im Lese- und Schreibmodus öffnen (so etwas wie /dev/pts/n).

Diese Geräte sind auch nicht suchbar. binaryWenn die Eingabe über das Terminal einwandfrei funktioniert, liegt das Problem möglicherweise nicht in der Suche.

Wenn die 14 gemeint ist eine errno sein (einen Fehlercode gesetzt durch Systemaufrufe nicht an ), dann auf den meisten Systemen, wäre die EFAULT( Bad - Adresse ). Der read()Systemaufruf schlägt mit diesem Fehler fehl, wenn er zum Einlesen einer nicht beschreibbaren Speicheradresse aufgefordert wird. Das wäre unabhängig davon, ob der fd die Daten von Punkten in eine Pipe oder eine reguläre Datei liest und würde generell einen Bug 1 anzeigen .

binaryErmittelt möglicherweise den Typ der Datei, die im Standardverzeichnis (mit fstat()) geöffnet ist, und stößt auf einen Fehler, wenn es sich weder um eine reguläre Datei noch um ein tty-Gerät handelt.

Schwer zu sagen, ohne mehr über die Anwendung zu wissen. Laufen sie unter strace(oder truss/ tuscäquivalent auf Ihrem System) könnte dazu beitragen , uns zu sehen , was der Systemaufruf, wenn überhaupt , die hier versagt.


1 Das von Matthew Ife in einem Kommentar zu Ihrer Frage ins Auge gefasste Szenario klingt hier sehr plausibel. Zitiert ihn:

Ich vermute, es wird versucht, bis zum Ende der Datei eine Puffergröße für das Lesen der Daten zu erhalten, die Tatsache, dass das Suchen nicht funktioniert, schlecht zu handhaben und zu versuchen, eine negative Größe zuzuweisen (kein schlechtes Malloc zu handhaben). Das Übergeben des Puffers, um zu lesen, welche Fehler im Puffer vorliegen, ist ungültig.

Stéphane Chazelas
quelle
14
Sehr interessant ... dies ist das erste Mal, dass ich gehört habe, dass umgeleitete Standardeingaben im Stil von ./binary < filesuchbar sind!
David Z
2
@DavidZ Es ist eine Datei, die bearbeitet wurde, openund es verhält sich genauso wie jede Datei, die bearbeitet wurde open. Es wurde zufällig von einem übergeordneten Prozess geerbt, aber das ist nicht so ungewöhnlich.
Hobbs
3
Wenn das System strace oder ein ähnliches Tool enthält , kann damit überprüft werden, bei welchem ​​Systemaufruf die Binärdatei fehlschlägt.
Pabouk
2
"Es kann es auch abschneiden, mappen, Löcher hinein stanzen usw." - Nein. Die Datei ist schreibgeschützt geöffnet. Das Programm müsste es dazu im Schreibmodus öffnen. Es kann jedoch nicht im Schreibmodus geöffnet werden, da es weder eine Schnittstelle gibt, über die dies direkt ausgeführt werden kann, noch eine Schnittstelle, über die "der" Verzeichniseintrag gesucht werden kann, der einer geöffneten Datei entspricht (was ist, wenn zwei solcher Einträge vorhanden sind oder Null?). . Es müsste die Datei angeben und dann das Dateisystem nach einem Objekt mit derselben Inode-Nummer durchsuchen. Das wäre außerordentlich langsam.
Kevin
1
@Stéphanechazelas: ach open("/proc/self/fd/0", O_RDWR)ja , funktioniert auch bei gelöschten dateien. Dumm mich: 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 foohebt die Verknüpfung auf, foobevor a.out mit der von umgeleiteten stdin ausgeführt wird foo.
Peter Cordes
46

Hier ist ein einfaches Beispielprogramm, das die Antwort von Stéphane Chazelas anhand lseek(2)seiner Eingabe veranschaulicht :

#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);
}

Testen:

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

Pipes sind nicht suchbar, und an dieser Stelle könnte sich ein Programm über Pipes beschweren.

muru
quelle
21

Die Pipe und die Umleitung sind sozusagen verschiedene Tiere. Wenn Sie here-documleiten ( <<) oder stdin umleiten, kommt < der Text nicht aus dem Nichts - er geht tatsächlich in einen Dateideskriptor (oder eine temporäre Datei, wenn Sie so wollen), und dort zeigt das stdin der Binärdatei.

Im Einzelnen ist hier ein Auszug aus dem bash'sQuellcode der Datei redir.c (Version 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)

Da die Umleitung im Grunde genommen als Dateien behandelt werden kann, können die Binärdateien sie navigieren oder seek()einfach durch die Datei springen und zu einem beliebigen Byte der Datei springen.

Pipes sind Pufferspeicher von 64 KB (zumindest unter Linux) mit Schreibzugriffen von 4096 Byte oder weniger, die garantiert atomar sind. Sie können also nicht frei navigieren, sondern nur sequentiell lesen. Ich habe einmal tailBefehl in Python implementiert . 29 Millionen Textzeilen können in Mikrosekunden gesucht werden, wenn sie umgeleitet werden, aber wenn sie catüber eine Pipe gesendet werden , ist nichts zu tun. Daher muss alles nacheinander gelesen werden.

Eine andere Möglichkeit besteht darin, dass die Binärdatei eine bestimmte Datei öffnen und keine Eingabe von einer Pipe empfangen möchte. Dies geschieht normalerweise über einen fstat()Systemaufruf und überprüft, ob die Eingabe von einem S_ISFIFODateityp stammt (der eine Pipe / Named Pipe bezeichnet).

Ihre spezifische Binärdatei versucht wahrscheinlich zu suchen, kann aber keine Pipes suchen, da wir nicht wissen, was es ist. Es wird empfohlen, die Dokumentation zu Rate zu ziehen, um herauszufinden, was genau Fehlercode 14 bedeutet.

HINWEIS : Einige Shells, wie beispielsweise dash (Debian Almquist Shell, standardmäßig /bin/shunter Ubuntu), implementieren here-docdie interne Umleitung von Pipes und sind daher möglicherweise nicht suchbar. Der Punkt bleibt derselbe - Pipes sind sequentiell und können nicht einfach navigiert werden, und Versuche, dies zu tun, führen zu Fehlern.

Sergiy Kolodyazhnyy
quelle
Stephanes Antwort besagt, dass Here-Docs mit Pipes implementiert werden können und dass einige gängige Shells dashdies tun. Diese Antwort erklärt das beobachtete Verhalten bei Bash, aber dieses Verhalten ist anscheinend nicht für alle anderen Shells garantiert.
Peter Cordes
@PeterCordes das ist absolut so, und ich habe es gerade mit dashauf meinem System überprüft . Das war mir vorher nicht bewusst. Vielen Dank für den Hinweis
Sergiy Kolodyazhnyy
Ein weiterer Kommentar: Sie würden fstat()stdin verwenden, um zu prüfen, ob es sich um eine Pipe handelt. statnimmt einen Pfadnamen. Aber wirklich, nur zu versuchen, lseekist der wahrscheinlich vernünftigste Weg, um festzustellen, ob ein fd suchbar ist, nachdem er bereits geöffnet ist.
Peter Cordes
5

Der Hauptunterschied liegt in der Fehlerbehandlung.

Im folgenden Fall wird der Fehler gemeldet

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

Im folgenden Fall wird der Fehler nicht gemeldet.

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

Mit bash können Sie weiterhin PIPESTATUS verwenden:

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

Es ist jedoch erst unmittelbar nach der Ausführung des Befehls verfügbar:

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

Es gibt einen weiteren Unterschied, wenn wir Shell-Funktionen anstelle von Binärdateien verwenden. In bashwerden Funktionen, die Teil einer Pipeline sind, in Unterschalen ausgeführt (mit Ausnahme der letzten Pipelinekomponente, wenn die lastpipeOption aktiviert und bashnicht interaktiv ist), sodass die Änderung von Variablen in der übergeordneten Shell keine Auswirkungen hat:

$ 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
Vouze
quelle
4
Sie zeigen also, dass die Fehlerbehandlung mit >der Shell erfolgt, mit der Pipe jedoch mit einem Befehl, der Text erzeugt. OKAY. Bei dieser speziellen Frage verwendet OP jedoch eine vorhandene Datei, sodass dies nicht der Fall ist und der Fehler eindeutig durch die Binärdatei verursacht wird.
Sergiy Kolodyazhnyy
1
Obwohl dies größtenteils nebensächlich ist, hat diese Antwort im allgemeinen Fall eine gewisse Relevanz für diese Frage und ist größtenteils richtig, sodass ich nicht der Meinung bin, dass sie diese Ablehnung verdient.
Stéphane Chazelas
@ Serg: Wenn Sie die Shell als Befehlszeile verwenden, ist dies nicht wichtig. In Skripten kann der Umgang mit Fehlern jedoch sehr wichtig sein.
Vouze