Die folgenden Bash-Befehle gehen in eine Endlosschleife:
$ echo hi > x
$ cat x >> x
Ich kann mir vorstellen, dass es cat
weiter liest, x
nachdem es angefangen hat, an stdout zu schreiben. Verwirrend ist jedoch, dass meine eigene Testimplementierung von cat ein anderes Verhalten aufweist:
// mycat.c
#include <stdio.h>
int main(int argc, char **argv) {
FILE *f = fopen(argv[1], "rb");
char buf[4096];
int num_read;
while ((num_read = fread(buf, 1, 4096, f))) {
fwrite(buf, 1, num_read, stdout);
fflush(stdout);
}
return 0;
}
Wenn ich renne:
$ make mycat
$ echo hi > x
$ ./mycat x >> x
Es wird keine Schleife ausgeführt. Angesichts des Verhaltens von cat
und der Tatsache, dass ich stdout
vorher einen Flush ausführte fread
, würde ich erwarten, dass dieser C-Code in einem Zyklus weiter liest und schreibt.
Wie stimmen diese beiden Verhaltensweisen überein? Welcher Mechanismus erklärt, warum cat
Schleifen ausgeführt werden, während der obige Code dies nicht tut?
shell
files
io-redirection
cat
Tyler
quelle
quelle
cat x >> x
die einen Fehler verursacht. Dieser Befehl wird jedoch in Kernighans und Pikes Unix-Buch als Übung vorgeschlagen.cat
verwendet höchstwahrscheinlich Systemaufrufe anstelle von stdio. Mit stdio kann Ihr Programm EOFness zwischenspeichern. Wenn Sie mit einer Datei beginnen, die größer als 4096 Byte ist, erhalten Sie eine Endlosschleife?Antworten:
Auf einem älteren RHEL System , das ich habe,
/bin/cat
tut nicht Schleife fürcat x >> x
.cat
gibt die Fehlermeldung "cat: x: Eingabedatei ist Ausgabedatei" aus. Ich kann täuschen ,/bin/cat
indem Sie diese:cat < x >> x
. Wenn ich Ihren obigen Code ausprobiere, erhalte ich die von Ihnen beschriebene "Schleife". Ich habe auch einen Systemaufruf geschrieben, der auf "cat" basiert:Dies ist auch eine Schleife. Die einzige Pufferung hier (im Gegensatz zu stdio-basierten "mycat") ist, was im Kernel vor sich geht.
Ich denke, was passiert ist, dass Dateideskriptor 3 (das Ergebnis von
open(av[1])
) einen Versatz in die Datei von 0 hat. Abgelegtes Deskriptor 1 (stdout) hat einen Versatz von 3, weil das ">>" die aufrufende Shell veranlasst, einlseek()
auf dem Dateideskriptor, bevor er an dencat
untergeordneten Prozess übergeben wird.Wenn Sie eine
read()
beliebige Art ausführen, sei es in einem Stdio-Puffer oder in einer Ebene, wirdchar buf[]
die Position des Dateideskriptors vorgerückt. 3. Wenn Sie eine beliebige Art ausführen, wirdwrite()
die Position des Dateideskriptors vorgerückt. Aufgrund des ">>" hat Dateideskriptor 1 immer einen Versatz, der größer oder gleich dem Versatz von Dateideskriptor 3 ist. Daher wird jedes "katzenartige" Programm eine Schleife ausführen, es sei denn, es führt eine interne Pufferung durch. Es ist möglich, vielleicht sogar wahrscheinlich, dass eine stdio-Implementierung von aFILE *
(das ist der Typ der Symbolestdout
undf
in Ihrem Code) einen eigenen Puffer enthält.fread()
kann tatsächlich einen Systemaufruf ausführenread()
, um den internen Puffer fo zu füllenf
. Dies kann oder kann nichts an den Innenseiten von ändernstdout
. Ich rufefwrite()
anstdout
kann oder kann nichts innerhalb von ändernf
. Eine stdio-basierte "Katze" kann also keine Schleife bilden. Oder es könnte. Schwer zu sagen, ohne viel hässlichen, hässlichen libc-Code durchzulesen.Ich habe eine
strace
auf dem RHELcat
- es macht nur eine Abfolge vonread()
undwrite()
Systemaufrufen. Aber socat
muss man nicht arbeiten. Es wäre dann möglichmmap()
die Eingabedatei zu machenwrite(1, mapped_address, input_file_size)
. Der Kernel würde die ganze Arbeit machen.sendfile()
Auf Linux-Systemen können Sie auch einen Systemaufruf zwischen den Eingabe- und Ausgabedateideskriptoren ausführen. Es wurde gemunkelt, dass alte SunOS 4.x-Systeme den Memory-Mapping-Trick ausführen, aber ich weiß nicht, ob jemals jemand eine sendfile-basierte Katze erstellt hat. In jedem Fall wäre die „Looping“ nicht passieren, da beidewrite()
undsendfile()
erfordern eine Länge-zu-Übertragungsparameter.quelle
fread
Anruf eine EOF-Flagge zwischengespeichert, wie Mark Plotnick vorgeschlagen hatte. Beweise: [1] Darwin-Katze verwendet Read, nicht Fread; und [2] Darwins Stirnrunzeln ruft __srefill auf, wasfp->_flags |= __SEOF;
in einigen Fällen einstellt . [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…cat
istcat -u
- u für ungepufferte .>>
sollte es implementiert werden, indem open () mit demO_APPEND
Flag aufgerufen wird, was bewirkt, dass jede Schreiboperation (atomar) an das aktuelle Ende der Datei schreibt, unabhängig davon, an welcher Position sich der Dateideskriptor vor dem Lesen befand. Dieses Verhalten ist erforderlichfoo >> logfile & bar >> logfile
, um beispielsweise ordnungsgemäß zu funktionieren. Sie können es sich nicht leisten, davon auszugehen, dass die Position nach dem Ende Ihres letzten Schreibvorgangs immer noch das Ende der Datei ist.Eine moderne Katzenimplementierung (sunos-4.0 1988) verwendet mmap (), um die gesamte Datei abzubilden, und ruft dann 1x write () für diesen Bereich auf. Eine solche Implementierung führt keine Schleife aus, solange der virtuelle Speicher die Zuordnung der gesamten Datei ermöglicht.
Bei anderen Implementierungen hängt es davon ab, ob die Datei größer als der E / A-Puffer ist.
quelle
cat
Implementierungen puffern ihre Ausgabe nicht (-u
impliziert). Diese werden immer wiederholt.Wie in Bash-Fallstricke geschrieben , können Sie nicht aus einer Datei lesen und in derselben Pipeline darauf schreiben.
Die Lösung besteht darin, entweder einen Texteditor oder eine temporäre Variable zu verwenden.
quelle
Sie haben eine Art Rennbedingung zwischen beiden
x
. Einige Implementierungen voncat
(zB coreutils 8.23) verbieten Folgendes:Wird dies nicht erkannt, hängt das Verhalten natürlich von der Implementierung ab (Puffergröße usw.).
In Ihrem Code können Sie versuchen, ein
clearerr(f);
nach dem einzufügenfflush
, falls das nächstefread
einen Fehler zurückgibt, wenn das Kennzeichen für das Dateiende gesetzt ist.quelle
i = i++;
undefinierte Verhalten von C , daher die Diskrepanz.cat
.