Warum wird $ ls > ls.out
"ls.out" in die Liste der Dateinamen im aktuellen Verzeichnis aufgenommen? Warum wurde dies so gewählt? Warum nicht anders
ls > ../ls.out
Warum wird $ ls > ls.out
"ls.out" in die Liste der Dateinamen im aktuellen Verzeichnis aufgenommen? Warum wurde dies so gewählt? Warum nicht anders
ls > ../ls.out
Antworten:
Bei der Auswertung des Befehls wird zuerst die >
Umleitung aufgelöst: Zum Zeitpunkt der ls
Ausführung wurde die Ausgabedatei bereits erstellt.
Dies ist auch der Grund, warum das Lesen und Schreiben in dieselbe Datei unter Verwendung einer >
Umleitung innerhalb desselben Befehls die Datei abschneidet. Zum Zeitpunkt der Befehlsausführung wurde die Datei bereits abgeschnitten:
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
Tricks, um dies zu vermeiden:
<<<"$(ls)" > ls.out
(Funktioniert für alle Befehle, die ausgeführt werden müssen, bevor die Umleitung aufgelöst wird.)
Die Befehlsersetzung wird ausgeführt, bevor der äußere Befehl ausgewertet wird. Sie wird daher ls
ausgeführt, bevor er ls.out
erstellt wird:
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
ls | sponge ls.out
(Funktioniert für alle Befehle, die ausgeführt werden müssen, bevor die Umleitung aufgelöst wird.)
sponge
schreibt nur in die Datei, wenn der Rest der Pipe fertig ausgeführt wurde, wird also ls
ausgeführt, bevor sie ls.out
erstellt wird ( sponge
wird mit dem moreutils
Paket geliefert ):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(funktioniert für ls > ls.out
den speziellen Fall)
Die Dateinamenerweiterung wird ausgeführt, bevor die Umleitung aufgelöst wird. Sie wird daher ls
mit den Argumenten ausgeführt, die Folgendes nicht enthalten ls.out
:
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
Warum Umleitungen aufgelöst werden, bevor das Programm / Skript / was auch immer ausgeführt wird, ich sehe keinen bestimmten Grund, warum dies obligatorisch ist , aber ich sehe zwei Gründe, warum es besser ist , dies zu tun:
Wird STDIN nicht vorher umgeleitet, wird das Programm / Skript / was auch immer angehalten, bis STDIN umgeleitet wird.
Wenn Sie STDOUT nicht vorher umleiten, sollte die Shell die Ausgabe des Programms / Skripts / was auch immer zwischenspeichern, bis STDOUT umgeleitet wird.
Also Zeitverschwendung im ersten Fall und Zeit- und Gedächtnisverschwendung im zweiten Fall.
Dies ist genau das, was mir einfällt. Ich behaupte nicht, dass dies die eigentlichen Gründe sind. aber ich schätze, alles in allem, wenn man die Wahl hätte, würden sie aus den oben genannten Gründen sowieso vorher umleiten.
Von man bash
:
Weiterleitung
Bevor ein Befehl ausgeführt wird, können seine Eingabe und Ausgabe unter Verwendung einer speziellen Notation umgeleitet werden, die von der Shell interpretiert wird. Durch die Umleitung können die Dateizugriffsnummern von Befehlen dupliziert, geöffnet, geschlossen und so erstellt werden, dass sie auf verschiedene Dateien verweisen. Außerdem können Sie die Dateien ändern, aus denen der Befehl liest und in die er schreibt.
Der erste Satz legt nahe, dass die Ausgabe an einen anderen Ort als stdin
mit Umleitung gesendet wird, bevor der Befehl ausgeführt wird. Um in eine Datei umgeleitet zu werden, muss die Datei zuerst von der Shell selbst erstellt werden.
Um zu vermeiden, dass eine Datei vorhanden ist, sollten Sie die Ausgabe zuerst in die Named Pipe und dann in die Datei umleiten. Beachten Sie die Verwendung von &
, um die Kontrolle über das Terminal an den Benutzer zurückzugeben
DIR:/xieerqi
skolodya@ubuntu:$ mkfifo /tmp/namedPipe.fifo
DIR:/xieerqi
skolodya@ubuntu:$ ls > /tmp/namedPipe.fifo &
[1] 14167
DIR:/xieerqi
skolodya@ubuntu:$ cat /tmp/namedPipe.fifo > ls.out
Aber wieso?
Denken Sie darüber nach - wo wird die Ausgabe sein? Ein Programm hat Funktionen wie printf
, sprintf
, puts
, die alle standardmäßig unterwegs stdout
, kann aber ihre Ausgabe in Datei verschwunden sein , wenn die Datei in erster Linie gar nicht existiert? Es ist wie mit Wasser. Kannst du ein Glas Wasser bekommen, ohne zuerst Glas unter den Wasserhahn zu stellen?
Ich bin mit den aktuellen Antworten nicht einverstanden. Die Ausgabedatei muss geöffnet werden, bevor der Befehl ausgeführt wird, oder der Befehl muss nirgendwo seine Ausgabe schreiben.
Dies liegt daran, dass "alles eine Datei ist" in unserer Welt. Die Ausgabe auf dem Bildschirm erfolgt über SDOUT (auch Dateideskriptor 1 genannt). Damit eine Anwendung auf das Terminal schreiben kann, wird fd1 geöffnet und wie eine Datei darauf geschrieben.
Wenn Sie die Ausgabe einer Anwendung in einer Shell umleiten, ändern Sie fd1 so, dass es tatsächlich auf die Datei zeigt. Wenn Sie eine Pipe erstellen, ändern Sie das STDOUT einer Anwendung in das STDIN einer anderen Anwendung (fd0).
Aber es ist alles schön, das zu sagen, aber man kann ziemlich leicht sehen, wie das funktioniert strace
. Es ist ziemlich schweres Zeug, aber dieses Beispiel ist ziemlich kurz.
strace sh -c "ls > ls.out" 2> strace.out
Innerhalb sehen strace.out
wir folgende Highlights:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Dies öffnet sich ls.out
als fd3
. Schreibe nur Schneidet ab (überschreibt), falls vorhanden, andernfalls wird erstellt.
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
Das ist ein bisschen Jonglieren. Wir schalten STDOUT (fd1) auf fd10 um und schließen es. Dies liegt daran, dass wir mit diesem Befehl nichts an das echte STDOUT ausgeben. Zum ls.out
Abschluss wird der Schreibpunkt dupliziert und der ursprüngliche Punkt geschlossen.
stat("/opt/wine-staging/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/home/oli/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0
Dies ist die Suche nach der ausführbaren Datei. Eine Lektion vielleicht, um keinen langen Weg zu haben;)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0961324a10) = 31933
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 31933
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31933, si_status=0, si_utime=0, si_stime=0} ---
rt_sigreturn() = 31933
dup2(10, 1) = 1
close(10) = 0
Dann wird der Befehl ausgeführt und das übergeordnete Element wartet. Während dieses Vorgangs wird jedes STDOUT tatsächlich dem geöffneten Datei-Handle zugeordnet ls.out
. Wenn das Kind Probleme hat SIGCHLD
, teilt dies dem übergeordneten Prozess mit, dass dieser beendet ist und wieder aufgenommen werden kann. Es endet mit ein wenig mehr Jonglieren und einem Abschluss von ls.out
.
Warum wird so viel jongliert? Nein, ich bin mir auch nicht ganz sicher.
Natürlich können Sie dieses Verhalten ändern. Sie könnten mit so etwas in den Speicher puffern sponge
und das wird vom Fortfahren-Befehl unsichtbar sein. Wir haben immer noch Auswirkungen auf die Dateideskriptoren, jedoch nicht auf eine für das Dateisystem sichtbare Weise.
ls | sponge ls.out
Es gibt auch einen schönen Artikel über die Implementierung von Umleitungs- und Pipe-Operatoren in der Shell . Was zeigt, wie eine Umleitung implementiert werden $ ls > ls.out
könnte, könnte so aussehen:
main(){
close(1); // Release fd no - 1
open("ls.out", "w"); // Open a file with fd no = 1
// Child process
if (fork() == 0) {
exec("ls");
}
}