Grundlegendes zu Bashs Befehl zum Ersetzen von Dateien

11

Ich versuche zu verstehen, wie genau Bash die folgende Zeile behandelt:

$(< "$FILE")

Laut der Bash-Manpage entspricht dies:

$(cat "$FILE")

und ich kann der Argumentationslinie für diese zweite Linie folgen. Bash führt eine Variablenerweiterung durch $FILE, gibt die Befehlssubstitution ein, übergibt den Wert von $FILEan cat, cat gibt den Inhalt der $FILEStandardausgabe aus, die Befehlssubstitution wird beendet, indem die gesamte Zeile durch die Standardausgabe ersetzt wird, die sich aus dem Befehl im Inneren ergibt, und Bash versucht, sie wie folgt auszuführen ein einfacher Befehl.

Für die erste Zeile, die ich oben erwähnt habe, verstehe ich es jedoch wie folgt: Bash führt eine Variablensubstitution durch $FILE, Bash öffnet $FILEzum Lesen der Standardeingabe, irgendwie wird die Standardeingabe in die Standardeingabe kopiert , die Befehlssubstitution wird beendet und Bash versucht, den resultierenden Standard auszuführen Ausgabe.

Kann mir bitte jemand erklären, wie der Inhalt von $FILEvon stdin zu stdout geht?

Stanley Yu
quelle

Antworten:

-3

Dies <ist nicht direkt ein Aspekt der Bash-Befehlsersetzung . Es ist ein Umleitungsoperator (wie eine Pipe), den einige Shells ohne Befehl zulassen (POSIX gibt dieses Verhalten nicht an).

Vielleicht wäre es mit mehr Leerzeichen klarer:

echo $( < $FILE )

Dies ist effektiv * dasselbe wie das POSIX-sicherere

echo $( cat $FILE )

... was auch effektiv ist *

echo $( cat < $FILE )

Beginnen wir mit der letzten Version. Dies wird catohne Argumente ausgeführt, was bedeutet, dass es von der Standardeingabe gelesen wird. $FILEwird aufgrund der in die Standardeingabe umgeleitet <, so dass catderen Inhalt in die Standardausgabe gestellt wird. Die $(command)Substitution drückt dann die catAusgabe in Argumente für echo.

In bash(aber nicht im POSIX-Standard) können Sie <ohne Befehl verwenden. bash(und zshund kshaber nicht dash) wird dies so interpretieren, als ob cat <, ohne einen neuen Unterprozess aufzurufen. Da dies für die Shell typisch ist, ist es schneller als das wörtliche Ausführen des externen Befehls cat. * Deshalb sage ich "effektiv das gleiche wie".

Adam Katz
quelle
Wenn Sie also im letzten Absatz sagen " bashwird das als interpretieren cat filename", meinen Sie, dass dieses Verhalten spezifisch für die Befehlssubstitution ist? Denn wenn ich < filenamealleine renne, macht Bash es nicht fertig. Es wird nichts ausgegeben und ich kehre zu einer Eingabeaufforderung zurück.
Stanley Yu
Ein Befehl wird noch benötigt. @cuonglm veränderte meinen ursprünglichen Text aus cat < filenamezu cat filenamedem ich entgegensetzen und zurückkommen kann.
Adam Katz
1
Eine Pipe ist eine Art Datei. Der Shell-Operator |erstellt eine Pipe zwischen zwei Unterprozessen (oder bei einigen Shells von einem Unterprozess zur Standardeingabe der Shell). Der Shell-Operator $(…)erstellt eine Pipe von einem Unterprozess zur Shell selbst (nicht zu ihrer Standardeingabe). Der Shell-Operator <enthält keine Pipe, sondern öffnet nur eine Datei und verschiebt den Dateideskriptor zur Standardeingabe.
Gilles 'SO - hör auf böse zu sein'
3
< fileist nicht dasselbe wie cat < file(außer zshwo es ist $READNULLCMD < file). < fileist perfekt POSIX und öffnet sich nur filezum Lesen und macht dann nichts ( fileist also sofort in der Nähe). Es ist $(< file)oder `< file`ist ein spezieller Operator von ksh, zshund bash(und das Verhalten wird in POSIX nicht angegeben). Siehe meine Antwort für Details.
Stéphane Chazelas
2
Um @ StéphaneChazelas 'Kommentar in ein anderes Licht zu rücken: In erster Näherung ist dies $(cmd1) $(cmd2)normalerweise dasselbe wie $(cmd1; cmd2). Aber schauen Sie sich den Fall an, wo cmd2ist < file. Wenn wir sagen $(cmd1; < file), wird die Datei nicht gelesen, aber mit $(cmd1) $(< file). Es ist also falsch zu sagen, dass dies $(< file)nur ein gewöhnlicher Fall $(command)mit einem Befehl von ist < file.   $(< …)ist ein Sonderfall der Befehlssubstitution und keine normale Verwendung der Umleitung.
Scott
13

$(<file)(funktioniert auch mit `<file`) ist ein spezieller Operator der Korn-Shell, der von zshund kopiert wird bash. Es sieht sehr nach Befehlsersetzung aus, ist es aber nicht wirklich.

In POSIX-Shells lautet ein einfacher Befehl:

< file var1=value1 > file2 cmd 2> file3 args 3> file4

Alle Teile sind optional. Sie können nur Umleitungen, nur Befehle, nur Zuweisungen oder Kombinationen verwenden.

Wenn es Umleitungen, aber keinen Befehl gibt, werden die Umleitungen ausgeführt (also > filewürde a geöffnet und abgeschnitten file), aber dann passiert nichts. So

< file

Öffnet sich filezum Lesen, aber dann passiert nichts, da es keinen Befehl gibt. Also fileist das dann geschlossen und das wars. Wenn $(< file)es sich um eine einfache Befehlsersetzung handelt , wird sie zu nichts erweitert.

In der POSIX-Spezifikation führt in $(script), wenn es scriptnur aus Umleitungen besteht, zu nicht spezifizierten Ergebnissen . Das soll das besondere Verhalten der Kornschale ermöglichen.

In ksh (hier getestet mit ksh93u+), wenn das Skript aus einem und nur einem einfachen Befehl besteht (obwohl Kommentare vorher und nachher zulässig sind), der nur aus Umleitungen besteht (kein Befehl, keine Zuweisung) und wenn die erste Umleitung ein stdin (fd) ist 0) einspeisen ( <, <<oder <<<) die Umleitung, so:

  • $(< file)
  • $(0< file)
  • $(<&3)(auch $(0>&3)eigentlich da das eigentlich der gleiche Operator ist)
  • $(< file > foo 2> $(whatever))

aber nicht:

  • $(> foo < file)
  • Noch $(0<> file)
  • Noch $(< file; sleep 1)
  • Noch $(< file; < file2)

dann

  • Alle außer der ersten Umleitung werden ignoriert (sie werden analysiert).
  • und es erweitert sich auf den Inhalt der Datei / heredoc / herestring (oder was auch immer aus dem Dateideskriptor gelesen werden kann, wenn Dinge wie verwendet werden <&3) abzüglich der nachfolgenden Zeilenumbruchzeichen.

als ob mit $(cat < file)außer dem

  • Das Lesen erfolgt intern durch die Shell und nicht durch cat
  • Es ist weder ein Rohr noch ein zusätzlicher Prozess erforderlich
  • Infolgedessen bleibt jede Änderung danach (wie in $(<${file=foo.txt})oder $(<file$((++n)))) , da der darin enthaltene Code nicht in einer Unterschale ausgeführt wird.
  • Lesefehler (jedoch keine Fehler beim Öffnen von Dateien oder beim Duplizieren von Dateideskriptoren) werden unbemerkt ignoriert.

In zsh, ist es die gleiche , außer dass das spezielle Verhalten nur dann ausgelöst wird , wenn gibt es nur eine Datei - Eingabe - Umleitung ( <fileoder 0< file, nein <&3, <<<here, < a < b...)

Außer beim Emulieren anderer Shells in:

< file
<&3
<<< here...

Das ist , wenn es nur Eingang Umleitungen ohne Befehle ist, außerhalb des Befehls Substitution, zshführt die $READNULLCMD(einen Pager durch Standard), und , wenn es sowohl Eingangs- und Ausgang Umleitungen, die $NULLCMD( catStandardeinstellung), so dass selbst wenn $(<&3)nicht als die besonderen erkannte Operator, es wird immer noch wie in funktionieren, kshindem ein Pager aufgerufen wird, um dies zu tun (dieser Pager verhält sich so, catda sein Standard eine Pipe ist).

Doch während ksh‚s $(< a < b)auf den Inhalt erweitern würde a, in zsh, dehnt es sich auf den Inhalt aund b(oder nur , bwenn die multiosOption deaktiviert ist), $(< a > b)würde kopieren azu bund erweitern , um nichts, usw.

bash hat einen ähnlichen Operator, aber mit ein paar Unterschieden:

  • Kommentare sind vorher erlaubt, aber nicht danach:

    echo "$(
       # getting the content of file
       < file)"
    

    funktioniert aber:

    echo "$(< file
       # getting the content of file
    )"
    

    dehnt sich zu nichts aus.

  • Wie in zsh, nur eine Datei stdin Umleitung, obwohl es kein Zurückgreifen auf a $READNULLCMDgibt $(<&3), $(< a < b)führen Sie die Umleitungen durch, aber erweitern Sie auf nichts.

  • Aus irgendeinem Grund wird bashzwar nicht aufgerufen cat, aber dennoch ein Prozess gegabelt, der den Inhalt der Datei durch eine Pipe führt, wodurch die Optimierung wesentlich geringer ist als in anderen Shells. Es ist in der Tat wie ein $(cat < file)Wo catwäre ein eingebautes cat.
  • Infolgedessen gehen alle darin vorgenommenen Änderungen nachträglich verloren (in den $(<${file=foo.txt})oben genannten Fällen geht beispielsweise diese $fileZuordnung nachträglich verloren).

In bash, IFS= read -rd '' var < file (funktioniert auch in zsh) ist eine effektivere Methode, um den Inhalt einer Textdatei in eine Variable einzulesen . Es hat auch den Vorteil, dass die nachgestellten Zeilenumbrüche beibehalten werden. Siehe auch $mapfile[file]in zsh(im zsh/mapfileModul und nur für reguläre Dateien), das auch mit Binärdateien funktioniert.

Beachten Sie, dass die pdksh-basierten Varianten von kshim Vergleich zu ksh93 einige Variationen aufweisen. Von Interesse in mksh(einer dieser von pdksh abgeleiteten Muscheln), in

var=$(<<'EOF'
That's multi-line
test with *all* sorts of "special"
characters
EOF
)

wird dahingehend optimiert, dass der Inhalt des hier vorliegenden Dokuments (ohne die nachgestellten Zeichen) erweitert wird, ohne dass eine temporäre Datei oder Pipe verwendet wird, wie dies sonst bei den hier verwendeten Dokumenten der Fall ist, was es zu einer effektiven mehrzeiligen Anführungszeichen-Syntax macht.

Für alle Versionen von und portierbar zu sein ksh, bedeutet , sich darauf zu beschränken, nur Kommentare zu vermeiden und zu berücksichtigen, dass Änderungen an Variablen, die innerhalb von vorgenommen wurden, beibehalten werden können oder nicht.zshbash$(<file)

Stéphane Chazelas
quelle
Ist es richtig, dass $(<)es sich um einen Operator für Dateinamen handelt? Ist <in $(<)einem Umleitungsoperator oder keinen Operator auf seinem eigenen, und muss Teil des gesamten Operators sein $(<)?
Tim
@ Tim, es spielt keine Rolle, wie Sie sie nennen möchten. $(<file)soll auf fileähnliche Weise auf den Inhalt von erweitert werden, wie dies der Fall $(cat < file)wäre. Wie es gemacht wird, variiert von Shell zu Shell, was in der Antwort ausführlich beschrieben wird. Wenn Sie möchten, können Sie sagen, dass es sich um einen speziellen Operator handelt, der ausgelöst wird, wenn eine Befehlssubstitution (syntaktisch) eine einzelne Standardumleitung (syntaktisch) enthält, jedoch wiederum mit Einschränkungen und Variationen, die von der hier aufgeführten Shell abhängen .
Stéphane Chazelas
@ StéphaneChazelas: Faszinierend wie immer; Ich habe das mit einem Lesezeichen versehen. Also, n<&mund n>&mdas Gleiche tun? Das wusste ich nicht, aber ich denke, es ist nicht allzu überraschend.
Scott
@ Scott, ja, beide machen eine dup(m, n). Ich kann einige Hinweise darauf sehen, dass ksh86 stdio verwendet, und einige fdopen(fd, "r" or "w"), also könnte es dann wichtig gewesen sein. Die Verwendung von stdio in einer Shell macht jedoch wenig Sinn, sodass ich nicht erwarte, dass Sie eine moderne Shell finden, in der dies einen Unterschied macht. Ein Unterschied besteht darin , dass >&nist dup(n, 1)(kurz für 1>&n), während <&nist dup(n, 0)(kurz für 0<&n).
Stéphane Chazelas
Richtig. Außer natürlich, dass die Zwei-Argument-Form des Dateideskriptor-Duplizierungsaufrufs aufgerufen wird dup2(); dup()nimmt nur ein Argument und verwendet wie open()der niedrigste verfügbare Dateideskriptor. (Heute habe ich erfahren, dass es eine dup3()Funktion gibt .)
Scott
8

Weil bashes intern für Sie erledigt, den Dateinamen erweitert und die Datei auf Standardausgabe katzt, wie wenn Sie es tun würden $(cat < filename). Es ist eine Bash-Funktion. Vielleicht müssen Sie sich den bashQuellcode ansehen , um genau zu wissen, wie er funktioniert.

Hier ist die Funktion, um diese Funktion zu handhaben (Aus bashQuellcode, Datei builtins/evalstring.c):

/* Handle a $( < file ) command substitution.  This expands the filename,
   returning errors as appropriate, then just cats the file to the standard
   output. */
static int
cat_file (r)
     REDIRECT *r;
{
  char *fn;
  int fd, rval;

  if (r->instruction != r_input_direction)
    return -1;

  /* Get the filename. */
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing++;
  fn = redirection_expand (r->redirectee.filename);
  if (posixly_correct && !interactive_shell)
    disallow_filename_globbing--;

  if (fn == 0)
    {
      redirection_error (r, AMBIGUOUS_REDIRECT);
      return -1;
    }

  fd = open(fn, O_RDONLY);
  if (fd < 0)
    {
      file_error (fn);
      free (fn);
      return -1;
    }

  rval = zcatfd (fd, 1, fn);

  free (fn);
  close (fd);

  return (rval);
}

Eine Notiz, $(<filename)die nicht genau gleichbedeutend ist mit $(cat filename); Letzteres schlägt fehl, wenn der Dateiname mit einem Bindestrich beginnt -.

$(<filename)war ursprünglich von kshund wurde hinzugefügt bashvon Bash-2.02.

cuonglm
quelle
1
cat filenameschlägt fehl, wenn der Dateiname mit einem Bindestrich beginnt, da cat Optionen akzeptiert. Mit den meisten modernen Systemen können Sie das umgehen cat -- filename.
Adam Katz
-1

Stellen Sie sich die Befehlsersetzung so vor, als würden Sie einen Befehl wie gewohnt ausführen und die Ausgabe an dem Punkt ausgeben, an dem Sie den Befehl ausführen.

Die Ausgabe von Befehlen kann als Argumente für einen anderen Befehl, zum Festlegen einer Variablen und sogar zum Generieren der Argumentliste in einer for-Schleife verwendet werden.

foo=$(echo "bar")setzt den Wert der Variablen $fooauf bar; die Ausgabe des Befehls echo bar.

Befehlsersetzung

Iyrin
quelle
1
Ich glaube, dass aus der Frage ziemlich klar hervorgeht, dass das OP die Grundlagen der Befehlssubstitution versteht. Die Frage $(< file)bezieht sich auf den Sonderfall von , und er benötigt kein Tutorial zum allgemeinen Fall. Wenn Sie sagen, dass dies $(< file)nur ein gewöhnlicher Fall $(command)mit einem Befehl von ist < file, dann sagen Sie dasselbe, was Adam Katz sagt , und Sie liegen beide falsch.
Scott