Warum funktioniert 'sed q' beim Lesen aus einer Pipe anders?

25

Ich habe eine Testdatei mit dem Namen 'test' erstellt, die Folgendes enthält:

xxx
yyy
zzz

Ich habe den Befehl ausgeführt:

(sed '/y/ q'; echo aaa; cat) < test

und ich bekam:

xxx
yyy
aaa
zzz

Dann bin ich gelaufen:

cat test | (sed '/y/ q'; echo aaa; cat)

und bekam:

xxx
yyy
aaa

Frage

sedLiest und druckt, bis es auf eine Zeile mit 'y' stößt, und stoppt dann. Im ersten, aber nicht im zweiten Fall liest und druckt cat den Rest.

Kann jemand erklären, welches Phänomen hinter diesem Verhaltensunterschied steckt?

Ich habe auch bemerkt, dass es in Ubuntu 16.04 und Centos 6 so funktioniert, aber in Centos 7 gibt kein Befehl 'zzz' aus.

Antti Kuusela
quelle
Ich vermute, dass cat(in der Subshell) der Dateideskriptor im ersten Fall wiederverwendet werden kann, da stdin an eine echte Datei gebunden ist. Im zweiten Fall stammt stdin aus einer Pipe und nicht aus einer echten Datei. Beachten Sie, dass auch (sed '/y/ q'; echo aaa; cat) < <(cat test)nicht gedruckt wird zzz.
Martin Nyolt
1
Ein einfacheres Beispiel: (head -n1; head -n1) < testundcat test | (head -n1; head -n1)
Martin Nyolt

Antworten:

22

Wenn eine Eingabedatei gesucht werden kann (wie das Lesen aus einer regulären Datei) oder nicht gesucht werden kann (wie das Lesen aus einer Pipe), sedverhalten sich (und andere Standarddienstprogramme) anders (siehe INPUT FILESAbschnitt in diesem Link ).

Zitat aus dem doc:

Wenn ein Standarddienstprogramm eine suchbare Eingabedatei liest und ohne Fehler beendet, bevor das Dateiende erreicht ist, muss das Dienstprogramm sicherstellen, dass der Dateiversatz in der Beschreibung der geöffneten Datei genau hinter dem letzten vom Dienstprogramm verarbeiteten Byte positioniert ist.

Also in:

(sed '/y/ q'; echo aaa; cat) < test

sedausgeführt qes Datei am Anfang der Offset links uit vor dem Erreichen EOF gebiete, so zzzLinie, so catweiter die Linien bleiben Druck (GNU sed ist nicht POSIX - konform in einem gewissen Zustand, siehe unten).

Und weiter aus dem Dokument:

Bei nicht durchsuchbaren Dateien ist der Status des Dateioffsets in der Beschreibung der geöffneten Datei für diese Datei nicht angegeben

In diesem Fall ist das Verhalten nicht angegeben. Die meisten Standardwerkzeuge, einschließlich sed, verbrauchen die Eingabe so weit wie möglich. Es liest die yyyZeile durch und beendet sie, qohne den Dateiversatz wiederherzustellen, so dass nichts mehr übrig bleibt cat.


GNU sedist nicht standardkonform, abhängig von der stdio-Implementierung des Systems und der glibc-Version:

$ (gsed '/y/ q'; echo aaa; cat) < test
xxx
yyy
aaa

Hier wurde das Ergebnis von Mac OSX 10.11.6, virtuellen Maschinen Centos 7.2 - glibc 2.17, Ubuntu 14.04 - glibc 2.19 erhalten, die auf Openstack mit CEPH-Backend ausgeführt werden.

Auf diesen Systemen können Sie die -uOption verwenden, um das Standardverhalten zu erreichen:

(gsed -u '/y/ q'; echo aaa; cat) </tmp/test

und für Pfeife:

$ cat test | (gsed -u '/y/ q'; echo aaa; cat)
xxx
yyy
aaa
zzz

Dies führt zu einer schrecklich ineffizienten Leistung, da sedjeweils ein Byte gelesen werden muss. Eine Teilausgabe von strace:

$ strace -fe read sh -c '{ sed -u "/y/q"; echo aaa; cat; } <test'
...
[pid  5248] read(3, "", 4096)           = 0
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
xxx
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
yyy
...
cuonglm
quelle
1
Für GNU sedhängt das von der stdio-Implementierung des Systems ab. Auf GNU-Systemen (mit der GNU-Libc) ist GNU sedkompatibel, und es exit()wird nach Dateien gesucht, die von stdio verwaltet werden.
Stéphane Chazelas
@ StéphaneChazelas: Wie kann ich das überprüfen? Mit meinem Centos 7.2, Ubuntu 14.04 VM, sedist nicht kompatibel, mein Manjaro-Laptop hat die gleiche sed Version 4.2.2
cuonglm
@ StéphaneChazelas: Klingt so, als wäre etwas unter der Haube passiert. strace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'Zeigen Sie auf meinen virtuellen Maschinen, dass no ausgeführt lseek()wurde, während in meinem Manjaro a lseek()zuvor aufgerufen wurde exit_group().
Cuonglm
Ich nehme an, das liegt an der Version der GNU libc. Sie können mit einem main() { char buf[999]; gets(buf); }'Programm testen .
Stéphane Chazelas
1
@ StéphaneChazelas: Bestätigt. Meine beiden VMs haben 2.17 und 2.19, während die in meinem Manjaro 2.23 ist. Handelt es sich hierbei um einen Glibc-Fehler? Haben Sie irgendwelche Informationen über den Wechsel zwischen den glibc-Versionen
cuonglm