Warum können einige eingebaute Shells "read" nicht die gesamte Zeile aus der Datei in "/ proc" lesen?

19

In einigen Bourne-wie Muscheln, die readkann builtin nicht die ganze Zeile aus der Datei eingelesen /proc(der Befehl unten ausgeführt werden sollte zsh, ersetzen Sie $=shellmit $shellanderen Shells):

$ for shell in bash dash ksh mksh yash zsh schily-sh heirloom-sh "busybox sh"; do
  printf '[%s]\n' "$shell"
  $=shell -c 'IFS= read x </proc/sys/fs/file-max; echo "$x"'       
done
[bash]
602160
[dash]
6
[ksh]
602160
[mksh]
6
[yash]
6
[zsh]
6
[schily-sh]
602160
[heirloom-sh]
602160
[busybox sh]
6

readStandard erfordert, dass die Standardeingabe eine Textdatei sein muss. Verursacht diese Anforderung das abwechslungsreiche Verhalten?


Lesen Sie die POSIX-Definition der Textdatei , ich mache eine Überprüfung:

$ od -t a </proc/sys/fs/file-max 
0000000   6   0   2   1   6   0  nl
0000007

$ find /proc/sys/fs -type f -name 'file-max'
/proc/sys/fs/file-max

Es gibt kein NULZeichen im Inhalt von /proc/sys/fs/file-maxund findes wird auch als reguläre Datei gemeldet (Ist dies ein Fehler in find?).

Ich denke, die Muschel hat etwas unter der Haube gemacht, wie file:

$ file /proc/sys/fs/file-max
/proc/sys/fs/file-max: empty
cuonglm
quelle

Antworten:

31

Das Problem ist, dass diese /procDateien unter Linux als Textdateien angezeigt werden stat()/fstat(), sich aber nicht als solche verhalten.

Da es sich um dynamische Daten handelt, können Sie nur einen read()Systemaufruf für sie ausführen (zumindest für einige von ihnen). Wenn Sie mehr als eine Aktion ausführen, erhalten Sie möglicherweise zwei Stücke mit zwei verschiedenen Inhalten. Stattdessen gibt eine Sekunde read()auf ihnen nichts zurück (das heißt, das Ende der Datei) (es sei denn, Sie kehren lseek()zum Anfang (und nur zum Anfang) zurück).

Das readDienstprogramm muss den Inhalt von Dateien jeweils byteweise lesen, um sicherzustellen, dass der Zeilenumbruch nicht überschritten wird. Das dashmacht:

 $ strace -fe read dash -c 'read a < /proc/sys/fs/file-max'
 read(0, "1", 1)                         = 1
 read(0, "", 1)                          = 0

Einige Shells wie bashhaben eine Optimierung, um so viele read()Systemaufrufe zu vermeiden . Sie prüfen zunächst, ob die Datei suchbar ist, und lesen dann Teile ein, da sie wissen, dass sie den Cursor direkt nach der neuen Zeile zurücksetzen können, wenn sie darüber hinausgelesen haben:

$ strace -e lseek,read bash -c 'read a' < /proc/sys/fs/file-max
lseek(0, 0, SEEK_CUR)                   = 0
read(0, "1628689\n", 128)               = 8

Mit hätten bashSie immer noch Probleme mit Proc-Dateien, die mehr als 128 Byte groß sind und nur in einem Lesesystemaufruf gelesen werden können.

bashscheint auch diese Optimierung zu deaktivieren, wenn die -dOption verwendet wird.

ksh93geht die Optimierung noch weiter und wird zum Schwindel. ksh93 readsucht zwar zurück, merkt sich aber die zusätzlichen Daten, die es für das nächste Mal gelesen hat read, sodass das nächste Mal read(oder eines seiner anderen eingebauten Programme, das Daten wie catoder liest head) nicht einmal versucht, readdie Daten zu verarbeiten (selbst wenn diese Daten von geändert wurden) andere Befehle dazwischen):

$ seq 10 > a; ksh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 2
$ seq 10 > a; sh -c 'read a; echo test > a; read b; echo "$a $b"' < a
1 st
Stéphane Chazelas
quelle
Ah ja, eine straceErklärung auf der Grundlage von ist viel einfacher zu verstehen!
Stephen Kitt
Dank machen dynamische Daten Sinn. Wie erkennt die Shell ihre dynamischen Daten? Wenn ich das tue cat /proc/sys/fs/file-max | ..., ist das Problem weg.
Sonntag,
3
Die Shell erkennt es nicht. Die Tatsache, dass es sich um dynamische Daten handelt, bedeutet, dass procfsmehrere aufeinanderfolgende read(2)Aufrufe derselben Datei nicht verarbeitet werden können. Das Verhalten hängt nicht von der Shell ab. Das Verwenden catund Weiterleiten funktioniert, da catdie Datei in ausreichend großen Blöcken gelesen wird. Die readeingebaute Shell liest dann Zeichen für Zeichen aus der Pipe.
Stephen Kitt
1
Es gibt eine etwas schmutzige Problemumgehung in mksh. read -N 10 a < /proc/sys/fs/file-max
Ipor Sircer
1
@IporSircer. Tatsächlich. Eine ähnliche Problemumgehung scheint zu funktionieren mit zsh: read -u0 -k10(oder verwenden sysread; $mapfile[/proc/sys/fs/file-max]funktioniert nicht, da diese Dateien nicht bearbeitet werden können mmap). In jedem Fall kann man bei jeder Schale immer dazu a=$(cat /proc/sys/fs/file-max). Mit einigen einschließlich mksh, zshund ksh93, a=$(</proc/sys/fs/file-max)funktioniert auch und Gabel keinen Prozess das Lesen zu tun.
Stéphane Chazelas
9

Wenn Sie wissen möchten, warum? Dies ist so, Sie können die Antwort in den Kernel-Quellen hier sehen :

    if (!data || !table->maxlen || !*lenp || (*ppos && !write)) {
            *lenp = 0;
            return 0;
    }

Grundsätzlich ist das Suchen ( *pposnicht 0) für reads ( !write) von sysctl-Werten, die Zahlen sind, nicht implementiert . Jedes Mal , wenn ein Lesen von getan wird /proc/sys/fs/file-max, wird die Routine in Frage __do_proc_doulongvec_minmax()wird aus dem Eintrag aufgerufen für file-maxin der Konfigurationstabelle in der gleichen Datei.

Andere Einträge, wie sie beispielsweise /proc/sys/kernel/poweroff_cmdimplementiert werden proc_dostring(), ermöglichen Suchvorgänge, sodass Sie dd bs=1problemlos darauf zugreifen und aus Ihrer Shell lesen können.

Beachten Sie, dass seit Kernel 2.6 die meisten /procLesevorgänge über eine neue API namens seq_file implementiert wurden und dies Suchvorgänge unterstützt, sodass z. B. das Lesen /proc/statkeine Probleme verursachen sollte. /proc/sys/Wie wir sehen können, verwendet die Implementierung diese API nicht.

meuh
quelle
3

Beim ersten Versuch sieht dies wie ein Fehler in den Shells aus, der weniger als eine echte Bourne-Shell zurückgibt, oder seine Derivate (sh, bosh, ksh, heirloom).

Die ursprüngliche Bourne-Shell versucht, einen Block (64 Byte) zu lesen. Neuere Bourne-Shell-Varianten lesen 128 Byte, aber sie beginnen erneut mit dem Lesen, wenn kein neues Zeilenzeichen vorhanden ist.

Hintergrund: / procfs und ähnliche Implementierungen (zB die montierte /etc/mtabvirtuelle Datei) haben dynamische Inhalte und ein stat()Anruf nicht Ursache der Neuschöpfung des dynamischen Inhalts zuerst. Aus diesem Grund kann die Größe einer solchen Datei (vom Lesen bis zur EOF) von der zurückgegebenen Datei abweichen stat().

Da der POSIX-Standard voraussetzt, dass Dienstprogramme jederzeit mit kurzen Lesevorgängen rechnen müssen , ist eine Software, die glaubt, dass ein Wert, der read()weniger als die bestellte Anzahl von Bytes zurückgibt, ein EOF-Hinweis ist, fehlerhaft. Ein korrekt implementiertes Dienstprogramm ruft read()ein zweites Mal auf, wenn es weniger als erwartet zurückgibt - bis eine 0 zurückgegeben wird. Im Falle des readeingebauten ist es natürlich ausreichend zu lesen, bis EOF oder bis ein angezeigt NLwird.

Wenn Sie trusseinen Truss-Klon ausführen , sollten Sie in der Lage sein, das falsche Verhalten für die Shells zu überprüfen, die nur 6in Ihrem Experiment zurückkehren.

In diesem speziellen Fall scheint es sich um einen Linux-Kernel-Fehler zu handeln, siehe:

$ sdd -debug bs=1 if= /proc/sys/fs/file-max 
Simple copy ...
readbuf  (3, 12AC000, 1) = 1
writebuf (1, 12AC000, 1)
8readbuf  (3, 12AC000, 1) = 0

sdd: Read  1 records + 0 bytes (total of 1 bytes = 0.00k).
sdd: Wrote 1 records + 0 bytes (total of 1 bytes = 0.00k).

Der Linux-Kernel gibt mit der Sekunde 0 zurückread und dies ist natürlich falsch.

Fazit: Shells, die zuerst versuchen, einen ausreichend großen Datenblock zu lesen, lösen diesen Linux-Kernel-Fehler nicht aus.

schily
quelle
OK, beendete die Antwort mit einer neuen Überprüfung für einen Linux-Kernel-Fehler.
Schily
Es ist kein Fehler, es ist ein Feature!
Guntram Blohm unterstützt Monica
Dies ist eine wirklich seltsame Behauptung.
Schily
Es wäre ein Feature, wenn es dokumentiert wäre. Lesen kernel.org/doc/Documentation/filesystems/proc.txt , ich sehe keine Dokumentation für das Verhalten. Das heißt, es funktioniert eindeutig so, wie es vom Implementierer beabsichtigt ist. Wenn dies als Fehler angesehen werden soll, liegt dies am Design und nicht an der Implementierung.
Charles Duffy
0

Die Dateien unter / proc verwenden manchmal NULL-Zeichen, um Felder in der Datei zu trennen. Es scheint, dass read damit nicht umgehen kann.

Tony George
quelle