Was ist der Vorteil der Verwendung von $ () anstelle von Backticks in Shell-Skripten?

175

Es gibt zwei Möglichkeiten, die Ausgabe der Befehlszeile in zu erfassen bash:

  1. Legacy Bourne Shell Backticks ``:

    var=`command`
  2. $() Syntax (die meines Wissens Bash-spezifisch ist oder zumindest von alten Nicht-POSIX-Shells wie dem ursprünglichen Bourne nicht unterstützt wird)

    var=$(command)

Gibt es einen Vorteil bei der Verwendung der zweiten Syntax im Vergleich zu Backticks? Oder sind die beiden zu 100% gleichwertig?

DVK
quelle
14
$()ist POSIX und wird von allen modernen Bourne-Shells unterstützt, z. B. Ksh, Bash, Ash, Dash, Zsh, Busybox. (Ein nicht so modernes ist Solaris /bin/sh, aber unter Solaris sollten Sie /usr/xpg4/bin/shstattdessen das moderne verwenden).
Jens
27
Außerdem ein Hinweis zur Verwendung von $()und Backticks in Aliasen. Wenn Sie alias foo=$(command)in Ihrem .bashrcdann commandausgeführt, wenn die Alias - Befehl selbst wird während laufen .bashrcInterpretation. Mit alias foo=`command`, commandwird jedes Mal ausgeführt wird , wird der Alias ausgeführt werden . Wenn Sie jedoch $mit dem $()Formular (z. B. alias foo=\$(command)) entkommen , wird es jedes Mal ausgeführt, wenn der Alias ​​ausgeführt wird, anstatt während der .bashrcInterpretation. Soweit ich das durch Testen beurteilen kann; Ich kann in den Bash-Dokumenten nichts finden, was dieses Verhalten erklärt.
Dirtside
4
@dirtside Welche Shell ist das? Ich habe Bash und POSIX-Shell getestet. Der Backtick wird ausgeführt, wenn ich eine Quelle habe. Einfaches Beispiel: alias curDate = `date` Nachdem ich es gefunden und curDate ausgeführt habe, erhalte ich die Nachricht, dass es beispielsweise den Befehl Mon (Quelle am Montag) nicht finden kann.
Thecarpy
@dirtside Es ist nicht wahr. Auch mit dem Alias ​​foo = `command` commandwird nur einmal ausgeführt. Ich habe es überprüft: function aaa () {printf date; echo aaa >> ~ / test.txt; } alias test1 = aaa. Die Funktion aaa wird nur einmal (nach jedem Login) ausgeführt, unabhängig davon, wie oft alias ( test1) ausgeführt wurde. Ich habe .bashrc (unter Debian 10) verwendet.
Vitaliydev
Keine Ahnung, es war die Version von Bash, die ich vor fünfeinhalb Jahren verwendet habe, die wahrscheinlich schon zu diesem Zeitpunkt ziemlich alt war. Es ist möglich, dass meine Tests falsch waren, aber es spielt jetzt kaum eine Rolle, da ich bezweifle, dass noch jemand die Version verwendet, die es gab.
Dirtside

Antworten:

156

Das wichtigste ist die Fähigkeit, sie zu verschachteln , Befehle innerhalb von Befehlen, ohne Ihre geistige Gesundheit zu verlieren, um herauszufinden, ob irgendeine Form der Flucht auf den Backticks funktioniert.

Ein Beispiel, wenn auch etwas erfunden:

deps=$(find /dir -name $(ls -1tr 201112[0-9][0-9]*.txt | tail -1l) -print)

Hier erhalten Sie eine Liste aller Dateien im /dirVerzeichnisbaum, die denselben Namen wie die früheste datierte Textdatei vom Dezember 2011 haben (a) .

Ein anderes Beispiel wäre etwa das Abrufen des Namens (nicht des vollständigen Pfads) des übergeordneten Verzeichnisses:

pax> cd /home/pax/xyzzy/plugh
pax> parent=$(basename $(dirname $PWD))
pax> echo $parent
xyzzy

(a) Da ein bestimmter Befehl möglicherweise nicht funktioniert, habe ich die Funktionalität nicht getestet. Wenn Sie mich dafür stimmen, haben Sie die Absicht aus den Augen verloren :-) Es ist nur als Illustration gedacht, wie Sie nisten können, nicht als fehlerfreies produktionsfertiges Snippet.

paxdiablo
quelle
86
Ich erwarte, dass der gesamte Code auf SO produktionsbereite Snippets sind, die gemäß den Zuverlässigkeitsstandards des NASA-Shuttle-Codes entwickelt wurden. Alles andere bekommt eine Flagge und eine Löschabstimmung.
DVK
1
@DVK Falls Sie keinen Scherz machen, bin ich nicht der Meinung, dass Code-Beiträge markiert werden sollten, wenn sie nicht automatisch von SO-Standardeinstellungen (CC-BY-SA- oder MIT-Lizenzen) neu lizenziert werden, um solche Garantien oder Zweckmäßigkeiten zu berücksichtigen. Ich würde stattdessen Code auf SO auf eigenes Risiko wiederverwenden und Beiträge nach Hilfsbereitschaft, technischen Vorzügen usw.
abstimmen
9
@chrstphrchvz, wenn Sie sich mein Profil ansehen, sehen Sie diesen kleinen Ausschnitt: "Der gesamte Code, den ich auf Stack Overflow poste, wird durch die Lizenz" Mach was immer du willst damit "abgedeckt, deren vollständiger Text lautet: Do whatever the heck you want with it." :-) Auf jeden Fall bin ich mir ziemlich sicher, dass es Humor von DVK war.
Paxdiablo
1
aber was ist, wenn es "cd / home / pax / xyzzy / plover" war? Würden Sie sich in einem Labyrinth von verwinkelten kleinen Passagen befinden, die alle unterschiedlich sind?
Duncan
@wchargin Wussten Sie, dass dieser Vorfall ein Hauptmotiv für das Hinzufügen verwendeter definierter Literale zur C ++ - Sprache war? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod
59

Angenommen, Sie möchten das lib-Verzeichnis finden, das dem gccInstallationsort entspricht. Du hast eine Wahl:

libdir=$(dirname $(dirname $(which gcc)))/lib

libdir=`dirname \`dirname \\\`which gcc\\\`\``/lib

Der erste ist einfacher als der zweite - verwenden Sie den ersten.

Jonathan Leffler
quelle
4
Es wäre gut, einige Zitate um diese Befehlsersetzungen zu sehen!
Tom Fenech
1
Zumindest für Bash gilt der Kommentar von @TomFenech nicht für Aufgaben. x=$(f); x=`f` benimm dich genauso wie x="$(f)"; x="`f`". Im Gegensatz dazu Array Zuordnung x=($(f)); x=(`f`) kann durchführen Splitting bei$IFS Zeichen wie erwartet , wenn Befehle aufgerufen wird . Dies ist praktisch ( x=1 2 3 4macht keinen Sinn), aber inkonsistent.
kdb
@kdb Sie haben Recht mit der x=$(f)Arbeit ohne Anführungszeichen. Ich hätte genauer sein sollen; Ich schlug vor, libdir=$(dirname "$(dirname "$(which gcc)")")/lib(Anführungszeichen um die inneren Befehlssubstitutionen) zu verwenden. Wenn Sie nicht zitiert werden, unterliegen Sie weiterhin der üblichen Wortaufteilung und Glob-Erweiterung.
Tom Fenech
37

Die backticks ( `...`) sind die Legacy-Syntax, die nur von den ältesten nicht POSIX-kompatiblen Bourne-Shells benötigt $(...)wird. Sie sind POSIX und werden aus mehreren Gründen bevorzugter:

  • Backslashes ( \) in Backticks werden auf nicht offensichtliche Weise behandelt:

    $ echo "`echo \\a`" "$(echo \\a)"
    a \a
    $ echo "`echo \\\\a`" "$(echo \\\\a)"
    \a \\a
    # Note that this is true for *single quotes* too!
    $ foo=`echo '\\'`; bar=$(echo '\\'); echo "foo is $foo, bar is $bar" 
    foo is \, bar is \\
  • Verschachtelte Zitate im Inneren $()sind weitaus praktischer:

    echo "x is $(sed ... <<<"$y")"

    anstatt:

    echo "x is `sed ... <<<\"$y\"`"

    oder etwas schreiben wie:

    IPs_inna_string=`awk "/\`cat /etc/myname\`/"'{print $1}' /etc/hosts`

    weil $()verwendet einen völlig neuen Kontext zum Zitieren

    Das ist nicht tragbar, da Bourne- und Korn-Muscheln diese Backslashes erfordern würden, während Bash und Dash dies nicht tun.

  • Die Syntax für das Ersetzen von Verschachtelungsbefehlen ist einfacher:

    x=$(grep "$(dirname "$path")" file)

    als:

    x=`grep "\`dirname \"$path\"\`" file`

    weil $() ein völlig neuer Kontext für das Zitieren erzwungen wird, ist jede Befehlsersetzung geschützt und kann für sich behandelt werden, ohne dass besondere Bedenken hinsichtlich des Zitierens und des Escapings bestehen. Bei der Verwendung von Backticks wird es nach zwei und mehr Levels immer hässlicher.

    Einige weitere Beispiele:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • Es löst ein Problem inkonsistenten Verhaltens bei der Verwendung von Backquotes:

    • echo '\$x' Ausgänge \$x
    • echo `echo '\$x'` Ausgänge $x
    • echo $(echo '\$x') Ausgänge \$x
  • Die Backticks-Syntax unterliegt historischen Einschränkungen für den Inhalt des eingebetteten Befehls und kann einige gültige Skripts, die Backquotes enthalten, nicht verarbeiten, während die neueren $() Formular jede Art von gültigem eingebettetem Skript verarbeiten kann.

    Diese ansonsten gültigen eingebetteten Skripte funktionieren beispielsweise nicht in der linken Spalte, sondern auf dem rechten IEEE :

    echo `                         echo $(
    cat <<\eof                     cat <<\eof
    a here-doc with `              a here-doc with )
    eof                            eof
    `                              )
    
    
    echo `                         echo $(
    echo abc # a comment with `    echo abc # a comment with )
    `                              )
    
    
    echo `                         echo $(
    echo '`'                       echo ')'
    `                              )

Daher sollte die Syntax für die Substitution von$ Befehlen mit Präfix die bevorzugte Methode sein, da sie mit einer sauberen Syntax visuell klar ist (verbessert die Lesbarkeit von Mensch und Maschine), verschachtelbar und intuitiv ist, ihre innere Analyse getrennt ist und auch konsistenter ist (mit alle anderen Erweiterungen, die in doppelten Anführungszeichen analysiert werden), wobei Backticks die einzige Ausnahme sind und` Charakter leicht getarnt werden kann" was das Lesen noch schwieriger macht, insbesondere bei kleinen oder ungewöhnlichen Schriftarten.

Quelle: Warum wird $(...)bevorzugt`...` (Backticks)?bei BashFAQ

Siehe auch:

Kenorb
quelle
1
Verschachtelte Anführungszeichen bei der Substitution im Akzentgravis-Stil sind eigentlich undefiniert. Sie können doppelte Anführungszeichen entweder außen oder innen verwenden, aber nicht beide, portabel. Muscheln interpretieren sie unterschiedlich; Einige erfordern einen Backslash, um ihnen zu entkommen, andere erfordern, dass sie nicht einem Backslash entkommen.
Mirabilos
23

Von Mann schlagen:

          $(command)
   or
          `command`

   Bash performs the expansion by executing command and replacing the com-
   mand  substitution  with  the  standard output of the command, with any
   trailing newlines deleted.  Embedded newlines are not deleted, but they
   may  be  removed during word splitting.  The command substitution $(cat
   file) can be replaced by the equivalent but faster $(< file).

   When the old-style backquote form of substitution  is  used,  backslash
   retains  its  literal  meaning except when followed by $, `, or \.  The
   first backquote not preceded by a backslash terminates the command sub-
   stitution.   When using the $(command) form, all characters between the
   parentheses make up the command; none are treated specially.
dgw
quelle
9

Zusätzlich zu den anderen Antworten

$(...)

fällt optisch besser auf als

`...`

Backticks sehen zu sehr nach Apostrophen aus; Dies hängt von der verwendeten Schriftart ab.

(Und wie ich gerade bemerkt habe, ist es viel schwieriger, Backticks in Inline-Codebeispiele einzugeben.)

Keith Thompson
quelle
2
Sie müssen eine seltsame Tastatur haben (oder ich?). Für mich ist es viel einfacher, Backticks einzugeben - sie sind die obere linke Ecke, keine UMSCHALT oder ALT erforderlich.
DVK
1
@ DVK: Ich habe über ihr Aussehen gesprochen, nicht über die einfache Eingabe. (Meine Tastatur ist wahrscheinlich die gleiche wie Ihre.) Nun, da Sie es erwähnen, denke ich, dass ich ein besseres Muskelgedächtnis für $ (und )als für Backtick habe. YMMV.
Keith Thompson
Nie in Bash programmiert (von altem ksh nach Perl übersprungen), also definitiv kein Speicher für diese spezielle Syntax :)
DVK
1
@DVK, ich dachte, Keith bezog sich auf die Tatsache, dass Nicht-Block-Code hier (Block-Code bedeutet die Verwendung von vier Leerzeichen am Anfang der Zeile) Backticks verwendet, um dies anzuzeigen, was es schwierig macht, Backticks in sie einzufügen, ein weiteres Beispiel für die Verschachtelungsschwierigkeiten :-) FWIW, Sie können feststellen, dass der Code und / oder die Code-Tags (die andere Art, Nicht-Block-Code zu erstellen) leichter Backticks enthalten können.
Paxdiablo
@Pax - verstanden. Duh! Ich war in der Tat aus irgendeinem Grund mental auf Blockcodes fixiert.
DVK
7

$() ermöglicht das Verschachteln.

out=$(echo today is $(date))

Ich denke, Backticks erlauben es nicht.

Shiplu Mokaddim
quelle
2
Sie können Backticks verschachteln. es ist viel schwieriger : out=`echo today is \`date\`` .
Jonathan Leffler
4

Es ist der POSIX-Standard, der die $(command)Form der Befehlssubstitution definiert . Die meisten heute verwendeten Shells sind POSIX-kompatibel und unterstützen diese bevorzugte Form gegenüber der archaischen Backtick-Notation. Der Befehlsersetzungsabschnitt (2.6.3) des Shell Language-Dokuments beschreibt Folgendes:

Durch die Befehlssubstitution kann die Ausgabe eines Befehls anstelle des Befehlsnamens selbst ersetzt werden. Die Befehlsersetzung erfolgt, wenn der Befehl wie folgt beigefügt ist:

$(command)

oder (rückzitierte Version):

`command`

Die Shell erweitert die Befehlssubstitution, indem sie den Befehl in einer Subshell-Umgebung ausführt (siehe Shell-Ausführungsumgebung ) und die Befehlssubstitution ( Befehlstext plus das einschließende "$ ()" oder Backquotes) durch die Standardausgabe des Befehls ersetzt Sequenzen von einem oder mehreren <newline>Zeichen am Ende der Ersetzung. Eingebettet<newline> Zeichen vor dem Ende der Ausgabe dürfen nicht entfernt werden. Sie können jedoch als Feldtrennzeichen behandelt und während der Feldaufteilung entfernt werden, abhängig vom Wert des IFS und dem gültigen Angebot. Wenn die Ausgabe Null-Bytes enthält, ist das Verhalten nicht angegeben.

Innerhalb des rückzitierten Stils der Befehlssubstitution <backslash>bleibt seine wörtliche Bedeutung erhalten, außer wenn gefolgt von: ' $', ' `' oder <backslash>. Die Suche nach dem passenden Backquote wird durch das erste nicht zitierte, nicht maskierte Backquote erfüllt. Wenn bei dieser Suche in einem Shell-Kommentar, einem Here-Dokument, einer eingebetteten Befehlsersetzung des $ ( Befehls- ) Formulars oder einer Zeichenfolge in Anführungszeichen ein nicht maskiertes Backquote gefunden wird , treten undefinierte Ergebnisse auf. Eine Zeichenfolge in einfachen oder doppelten Anführungszeichen, die innerhalb der `...`Sequenz " " beginnt, aber nicht endet, führt zu undefinierten Ergebnissen.

Mit dem Formular $ ( Befehl ) bilden alle Zeichen, die der offenen Klammer bis zur übereinstimmenden schließenden Klammer folgen, den Befehl . Jedes gültige Shell-Skript kann für Befehle verwendet werden , mit Ausnahme eines Skripts, das ausschließlich aus Umleitungen besteht und nicht angegebene Ergebnisse liefert.

Die Ergebnisse der Befehlssubstitution dürfen nicht für eine weitere Tildeerweiterung, Parametererweiterung, Befehlssubstitution oder arithmetische Erweiterung verarbeitet werden. Wenn eine Befehlssubstitution in doppelten Anführungszeichen erfolgt, dürfen für die Ergebnisse der Substitution keine Feldaufteilung und Pfadnamenerweiterung durchgeführt werden.

Befehlsersetzung kann verschachtelt werden. Um die Verschachtelung innerhalb der Backquoted-Version festzulegen, muss die Anwendung den inneren Backquotes <backslash>Zeichen vorangestellt haben. beispielsweise:

\`command\`

Die Syntax der Shell-Befehlssprache ist für Erweiterungen, die mit "$((", die eine arithmetische Erweiterung oder eine Befehlssubstitution einführen kann, die mit einer Unterschale beginnt. Die arithmetische Erweiterung hat Vorrang; das heißt, die Shell muss zuerst bestimmen, ob sie die Erweiterung als arithmetische Erweiterung analysieren kann, und die Erweiterung nur als Befehl analysieren Ersetzen, wenn festgestellt wird, dass die Erweiterung nicht als arithmetische Erweiterung analysiert werden kann. Die Shell muss bei dieser Bestimmung keine verschachtelten Erweiterungen auswerten. Wenn sie auf das Ende der Eingabe stößt, ohne bereits festgestellt zu haben, dass die Erweiterung nicht als arithmetische Erweiterung analysiert werden kann, wird die Shell behandelt die Erweiterung als unvollständige arithmetische Erweiterung und meldet einen Syntaxfehler. Eine konforme Anwendung muss sicherstellen, dass sie die "$( " und "getrennt werden.('in zwei Token (dh durch Leerzeichen trennen) in einer Befehlssubstitution, die mit einer Unterschale beginnt. Beispielsweise könnte eine Befehlssubstitution, die eine einzelne Unterschale enthält, wie folgt geschrieben werden:

$( (command) )

JRFerguson
quelle
4

Dies ist eine Legacy-Frage, aber ich habe ein perfekt gültiges Beispiel für $(...)Over gefunden `...`.

Ich habe einen Remotedesktop für Windows verwendet, auf dem Cygwin ausgeführt wird, und wollte ein Ergebnis eines Befehls durchlaufen. Leider konnte der Backtick-Charakter weder aufgrund des Remote-Desktops noch aufgrund von Cygwin selbst eingegeben werden.

Es ist vernünftig anzunehmen, dass ein Dollarzeichen und Klammern einfacher sind, solche seltsamen Einstellungen einzugeben.

Piotr Zierhoffer
quelle