Warum bewirkt 'ls> ls.out', dass 'ls.out' in die Namensliste aufgenommen wird?

26

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

Edward Torvalds
quelle
3
wahrscheinlich, weil es zuerst eine Datei ls.out erstellt und dann die Ausgabe darauf schreibt
Dimitri Podborski
1
Wenn Sie die Einbeziehung vermeiden möchten, können Sie die Ausgabedatei immer in einem anderen Verzeichnis speichern. Zum Beispiel könnten Sie das übergeordnete Verzeichnis (vorausgesetzt, Sie befinden sich nicht im Stammverzeichnis des Dateisystems) mit ls > ../ls.out
Elder Geek

Antworten:

36

Bei der Auswertung des Befehls wird zuerst die >Umleitung aufgelöst: Zum Zeitpunkt der lsAusfü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 lsausgeführt, bevor er ls.outerstellt 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.)

    spongeschreibt nur in die Datei, wenn der Rest der Pipe fertig ausgeführt wurde, wird also lsausgeführt, bevor sie ls.outerstellt wird ( spongewird mit dem moreutilsPaket geliefert ):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
  • ls * > ls.out(funktioniert für ls > ls.outden speziellen Fall)

    Die Dateinamenerweiterung wird ausgeführt, bevor die Umleitung aufgelöst wird. Sie wird daher lsmit 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.

kos
quelle
1
Beachten Sie, dass die Shell während der Umleitung die Daten nicht berührt (entweder bei der Eingabe- oder bei der Ausgabeumleitung). Es öffnet nur die Datei und übergibt den Dateideskriptor an das Programm.
Peter Green
11

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 stdinmit 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?

Sergiy Kolodyazhnyy
quelle
10

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.outwir folgende Highlights:

open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

Dies öffnet sich ls.outals 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.outAbschluss 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 spongeund 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
Oli
quelle
6

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.outkö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"); 
    }
}
Dimitri Podborski
quelle