Ist> & - effizienter als> / dev / null?

58

Gestern habe ich diesen SO-Kommentar gelesen , der besagt, dass in der Shell (zumindest bash) >&-"dasselbe Ergebnis hat wie" >/dev/null.

Dieser Kommentar bezieht sich tatsächlich auf den ABS-Leitfaden als Informationsquelle. Diese Quelle besagt jedoch, dass die >&-Syntax "Dateideskriptoren schließt".

Mir ist nicht klar, ob die beiden Aktionen, einen Dateideskriptor zu schließen und ihn auf das Null-Gerät umzuleiten, völlig gleichwertig sind. Meine Frage ist also: Sind sie?

Auf den ersten Blick scheint das Schließen eines Deskriptors wie das Schließen einer Tür zu sein, aber das Umleiten auf ein Null-Gerät öffnet eine Tür in die Schwebe! Die beiden scheinen mir nicht genau gleich zu sein, denn wenn ich eine geschlossene Tür sehe, werde ich nicht versuchen, etwas herauszuwerfen, aber wenn ich eine offene Tür sehe, gehe ich davon aus, dass ich es kann.

Mit anderen Worten, ich habe mich immer gefragt, ob das >/dev/nullbedeutet, dass cat mybigfile >/dev/nulltatsächlich jedes Byte der Datei verarbeitet und in die Datei geschrieben wird, /dev/nulldie es vergisst. Auf der anderen Seite, wenn die Shell auf einen geschlossenen Dateideskriptor stößt, glaube ich (bin mir aber nicht sicher), dass sie einfach nichts schreibt, obwohl die Frage bleibt, ob catimmer noch jedes Byte gelesen wird.

Dieser Kommentar sagt >&-und >/dev/null" sollte " gleich sein, aber es ist keine so durchschlagende Antwort auf mich. Ich hätte gerne eine verbindlichere Antwort mit einem Verweis auf den Standard- oder Quellkern oder nicht ...

Jamadagni
quelle
Wenn ich gemeinnützig sein wollte, würde ich sagen, dass der Kommentar nicht bedeutet, dass sie auf die gleiche Weise implementiert wurden, sondern dass beide das gleiche Endergebnis haben, nämlich dass Sie die Ausgabe des Programms nicht sehen können.
Barmar

Antworten:

71

Nein, Sie möchten die Dateideskriptoren 0, 1 und 2 auf keinen Fall schließen.

Wenn Sie dies tun, wird die Datei beim ersten Öffnen der Anwendung zu stdin / stdout / stderr ...

Zum Beispiel, wenn Sie:

echo text | tee file >&-

Wenn tee(zumindest einige Implementierungen, wie z. B. busybox ') die Datei zum Schreiben öffnet, wird sie in Dateideskriptor 1 (stdout) geöffnet. Also teeschreibe textzweimal in file:

$ echo text | strace tee file >&-
[...]
open("file", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 1
read(0, "text\n", 8193)                 = 5
write(1, "text\n", 5)                   = 5
write(1, "text\n", 5)                   = 5
read(0, "", 8193)                       = 0
exit_group(0)                           = ?

Es ist bekannt, dass dies Sicherheitslücken verursacht. Zum Beispiel:

chsh 2>&-

Und chsh(eine Setuid-Anwendung) kann dazu führen, dass Fehlermeldungen in geschrieben werden /etc/passwd.

Einige Tools und sogar einige Bibliotheken versuchen, sich dagegen zu schützen. Zum Beispiel verschiebt GNU teeden Dateideskriptor auf einen Wert über 2, wenn die Dateien, die zum Schreiben geöffnet werden, mit 0, 1, 2 belegt sind, während busybox dies teenicht tut .

Die meisten Tools melden, wenn sie nicht in stdout schreiben können (weil es beispielsweise nicht geöffnet ist), eine Fehlermeldung in stderr (in der Sprache des Benutzers, was eine zusätzliche Verarbeitung zum Öffnen und Parsen von Lokalisierungsdateien bedeutet ...) Dies ist erheblich weniger effizient und kann möglicherweise zum Fehlschlagen des Programms führen.

Effizienter geht es auf keinen Fall. Das Programm führt weiterhin einen write()Systemaufruf aus. Effizienter kann es nur sein, wenn das Programm nach dem ersten fehlgeschlagenen write()Systemaufruf das Schreiben an stdout / stderr aufgibt , aber das tun Programme im Allgemeinen nicht. Sie werden in der Regel entweder mit einem Fehler beendet oder versuchen es erneut.

Stéphane Chazelas
quelle
4
Ich denke, diese Antwort wäre noch besser, wenn der letzte Absatz ganz oben wäre (da dies die direkteste Antwort auf die Frage des OP ist), und dann wurde diskutiert, warum es eine schlechte Idee ist, auch wenn es größtenteils funktioniert hat. Aber ich nehme an, habe eine Gegenstimme. ;)
ein Lebenslauf
@ StéphaneChazelas: Wie Michael sagte, hätte ich das letzte Absatz oben erwartet, aber danke für die Klarstellung schafft es eigentlich wahrscheinlich nur Probleme. Ich denke also, eine Zusatzfrage wäre, wann das Schließen einer FD tatsächlich nützlich sein wird. Oder sollte ich das als separate Frage stellen?
Jamadagni
@jamadagni, siehe unix.stackexchange.com/search?q=user%3A22565+%223%3E%26-%22 für einige Beispiele
Stéphane Chazelas
1
@jamadagni Wenn der von Stéphane bereitgestellte Link die Frage nicht beantwortet, würde ich sagen, dass dies wie der Beginn einer separaten Frage klingt, da es nicht direkt mit der relativen Effizienz der beiden Methoden zusammenhängt.
ein Lebenslauf
1
Ich schätze, dass Stephane mit dieser wichtigen Sicherheitswarnung beginnt, da es weniger sichtbar wäre, wenn der letzte Absatz oben wäre. +1 von mir.
Olivier Dulac
14

IOW Ich habe mich immer gefragt, ob das >/dev/nullbedeutet, dass cat mybigfile >/dev/nulltatsächlich jedes Byte der Datei verarbeitet und in die Datei geschrieben wird, /dev/nulldie es vergisst.

Es ist keine vollständige Antwort auf Ihre Frage, aber so funktioniert es.

catLiest die benannte (n) Datei (en) oder Standardeingabe (n), wenn keine Dateien benannt sind, und gibt deren Inhalt an die Standardausgabe aus, bis ein EOF (einschließlich Standardeingabe) für die zuletzt benannte Datei auftritt. Das ist seine Aufgabe.

Durch das Hinzufügen >/dev/nullleiten Sie die Standardausgabe nach / dev / null um. Dies ist eine spezielle Datei (ein Geräteknoten), die alles, was darin geschrieben ist, wegwirft (und beim Lesen sofort EOF zurückgibt). Beachten Sie, dass die E / A-Umleitung von der Shell und nicht von jeder einzelnen Anwendung bereitgestellt wird und dass der Name / dev / null nichts Magisches ist, sondern nur das, was dort auf den meisten Unix-ähnlichen Systemen vorkommt .

Es ist auch wichtig zu beachten, dass die spezifischen Mechanismen der Geräteknoten von Betriebssystem zu Betriebssystem unterschiedlich sind, cat (was in einem GNU-System Coreutils bedeutet) jedoch plattformübergreifend ist (derselbe Quellcode muss mindestens unter Linux und ausgeführt werden) Hurd) und kann daher keine Abhängigkeiten zu bestimmten Betriebssystemkernen übernehmen. Darüber hinaus funktioniert es weiterhin, wenn Sie einen / dev / null-Alias ​​(unter Linux bedeutet dies einen Geräteknoten mit derselben Haupt- / Nebengerätenummer) mit einem anderen Namen erstellen. Und es gibt immer den Fall, dass an einer anderen Stelle geschrieben wird, die sich praktisch gleich verhält (z. B. / dev / zero).

Daraus folgt, dass catdie besonderen Eigenschaften von / dev / null nicht bekannt sind und dass die Umleitung wahrscheinlich überhaupt nicht bekannt ist, dass sie jedoch genau die gleiche Arbeit leisten muss: Sie liest die genannten Dateien und gibt den Inhalt von aus die / jene Datei (en) auf ihre Standardausgabe. Dass die Standardausgabe von catzufällig ins Leere geht, ist an sich nichts, catworum es geht.

ein CVn
quelle
2
Um Ihre Antwort zu erweitern: Ja, cat mybigfile > /dev/nullwird dazu führen cat, dass jedes Byte bigfilein den Speicher gelesen wird . Und für jedes nByte wird es aufgerufen write(1, buffer, n). Unbekannt für das catProgramm, writewird der absolut nichts tun (außer vielleicht für einige triviale Buchhaltung). Das Schreiben in /dev/nullerfordert nicht die Verarbeitung jedes Bytes.
G-Man
2
Ich erinnere mich, dass ich überwältigt war, als ich die Linux-Kernel-Quelle des / dev / null-Geräts gelesen habe. Ich hatte erwartet, dass es ein ausgeklügeltes System zum Freigeben () von Puffern usw. gibt, aber nein, es ist im Grunde genommen nur eine Rückkehr ().
Brian Minton
4
@ G-Man: Ich weiß nicht, dass Sie garantieren können, dass dies in jedem Fall der Fall ist. Ich kann jetzt keine Beweise finden, aber ich erinnere mich an eine Implementierung von beidem, catoder cpdas würde funktionieren, indem mmapgroße Teile der Quelldatei in den Speicher geschrieben und dann write()die zugeordnete Region aufgerufen werden. Wenn Sie schreiben /dev/null, write()kehrt der Aufruf sofort zurück, ohne die Seiten der Quelldatei zu beschädigen, sodass er niemals tatsächlich von der Festplatte gelesen wird.
Nate Eldredge
2
Auch so etwas wie GNU catläuft auf vielen Plattformen, aber ein gelegentlicher Blick auf den Quellcode zeigt viele #ifdefs: Es ist nicht buchstäblich derselbe Code, der auf allen Plattformen läuft, und es gibt viele systemabhängige Abschnitte.
Nate Eldredge
@NateEldredge: Interessanter Punkt, aber ich habe nur auf Michaels Antwort aufgebaut, also widersprechen Sie mir nicht so sehr, wie Sie Michael widersprechen.
G-Man