Qual è il vantaggio di usare $ () invece di backtick negli script di shell?


175

Esistono due modi per acquisire l'output della riga di comando in bash:

  1. Bacchette legacy Bourne ``:

    var=`command`
  2. $() sintassi (che per quanto ne so è specifica di Bash, o almeno non supportata da vecchie shell non POSIX come l'originale Bourne)

    var=$(command)

C'è qualche vantaggio nell'usare la seconda sintassi rispetto ai backtick? O i due sono completamente equivalenti al 100%?


15
$()è POSIX e supportato da tutte le moderne shell Bourne, ad esempio ksh, bash, ash, dash, zsh, busybox, lo chiami. (Uno non così moderno è Solaris /bin/sh, ma su Solaris ti assicureresti di usare /usr/xpg4/bin/shinvece il moderno ).
Jens,

27
Inoltre, una nota sull'uso $()e sui backtick negli alias. Se hai alias foo=$(command)nel tuo .bashrcallora commandverrà eseguito quando il comando alias stesso viene eseguito durante l' .bashrcinterpretazione. Con alias foo=`command`, commandverrà eseguito ogni volta che viene eseguito l'alias. Ma se si sfugge a $con il $()modulo (ad esempio alias foo=\$(command)), anche esso verrà eseguito ogni volta che viene eseguito l'alias, anziché durante l' .bashrcinterpretazione. Per quanto ne so, provando, comunque; Non riesco a trovare nulla nei documenti bash che spiegano questo comportamento.
Dirtside,

4
@dirtside Quale shell è questa, ho testato la shell bash e POSIX, il backtick viene eseguito quando eseguo il source. Esempio semplice: alias curDate = `date` Dopo averlo creato ed eseguito curDate, ricevo un messaggio che non trova il comando Mon (Sourced on Monday), ad esempio.
thararpy,

@dirtside Non è vero. Anche con alias foo = `command` commandviene eseguito solo una volta. L'ho controllato: funzione aaa () {data di stampa; echo aaa >> ~ / test.txt; } alias test1 = aaa. La funzione aaa viene eseguita una sola volta (dopo ogni accesso), indipendentemente da quante volte alias ( test1) è stato eseguito. Ho usato .bashrc (su Debian 10).
vitaliydev,

Non ne ho idea, era la versione di bash che usavo cinque anni e mezzo fa, che probabilmente era piuttosto vecchia anche a quel punto. È possibile che il mio test non sia corretto, ma ora non importa perché dubito che qualcuno stia ancora usando la versione che era.
Dirtside

Risposte:


156

Il principale è la possibilità di annidarli , comandi all'interno dei comandi, senza perdere la sanità mentale cercando di capire se una qualche forma di fuga funzionerà sui backtick.

Un esempio, sebbene in qualche modo inventato:

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

che ti darà un elenco di tutti i file nella /dirstruttura di directory che hanno lo stesso nome del primo file di testo datato di dicembre 2011 (a) .

Un altro esempio potrebbe essere quello di ottenere il nome (non il percorso completo) della directory principale:

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

(a) Ora quel comando specifico potrebbe non funzionare davvero, non ho testato la funzionalità. Quindi, se mi voti per questo, hai perso di vista l'intento :-) È inteso solo come un'illustrazione su come annidare, non come uno snippet pronto per la produzione privo di bug.


87
Mi aspetto che tutto il codice su SO sia frammenti pronti per la produzione, progettato secondo gli standard di affidabilità del codice shuttle della NASA. Niente di meno ottiene una bandiera e un voto di eliminazione.
DVK,

1
@DVK Nel caso in cui tu non stia scherzando, non sono d'accordo sul fatto che i contributi al codice debbano essere contrassegnati per non riuscire a ricollocare automaticamente dalle impostazioni predefinite SO (licenze CC-BY-SA o MIT) al fine di consentire tali garanzie o idoneità allo scopo. Vorrei invece riutilizzare il codice su SO a mio rischio e voto e contributi in base alla disponibilità, ai meriti tecnici, ecc.
chrstphrchvz,

9
@chrstphrchvz, se guardi il mio profilo, vedrai questo piccolo frammento: "Tutto il codice che pubblico su Stack Overflow è coperto dalla licenza" Fai qualunque cosa tu voglia con esso ", il cui testo completo è: Do whatever the heck you want with it." :-) In ogni caso, sono abbastanza sicuro che si tratti di umorismo da DVK.
paxdiablo,

1
ma se fosse "cd / home / pax / xyzzy / plover"? Ti ritroveresti in un labirinto di piccoli passaggi tortuosi, tutti diversi?
Duncan,

@wchargin sapevi che questo incidente è stato uno dei principali motivi per l'aggiunta di letterali definiti usati al linguaggio C ++? en.cppreference.com/w/cpp/language/user_literal
ThomasMcLeod

59

Supponiamo di voler trovare la directory lib corrispondente a dove gccè installato. Hai una scelta:

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

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

Il primo è più facile del secondo: usa il primo.


4
Sarebbe bello vedere alcune virgolette intorno a quelle sostituzioni di comandi!
Tom Fenech,

1
Almeno per bash, il commento di @TomFenech non si applica agli incarichi. x=$(f); x=`f` comportarsi come x="$(f)"; x="`f`". Al contrario, l'assegnazione matrice x=($(f)); x=(`f`) fare eseguire scissione a $IFScaratteri come previsto quando si richiama comandi. Questo è conveniente ( x=1 2 3 4non ha senso) ma incoerente.
kdb,

@kdb hai ragione sul x=$(f)lavoro senza virgolette. Avrei dovuto essere più specifico; Stavo proponendo di usare libdir=$(dirname "$(dirname "$(which gcc)")")/lib(virgolette intorno alle sostituzioni di comando interne ). Se lasciato non quotato, sei ancora soggetto alla solita suddivisione delle parole e all'espansione globale.
Tom Fenech,

38

Il backticks ( `...`) è la sintassi legacy richiesta solo dalla più antica delle bourne-shell non compatibili con $(...)POSIX ed è POSIX e più preferita per diversi motivi:

  • Le barre rovesciate ( \) all'interno dei backtick vengono gestite in modo non ovvio:

    $ 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 \\
  • La citazione annidata all'interno $()è molto più conveniente:

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

    invece di:

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

    o scrivendo qualcosa come:

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

    perché $()utilizza un contesto completamente nuovo per la quotazione

    che non è portatile come le conchiglie Bourne e Korn richiederebbero queste barre rovesciate, mentre Bash e Dash non lo fanno.

  • La sintassi per le sostituzioni dei comandi di annidamento è più semplice:

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

    di:

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

    perché $()impone un contesto completamente nuovo per la quotazione, quindi ogni sostituzione di comando è protetta e può essere trattata da sola senza particolare preoccupazione per la quotazione e la fuga. Quando si usano i backtick, diventa più brutto dopo due e sopra i livelli.

    Pochi altri esempi:

    echo `echo `ls``      # INCORRECT
    echo `echo \`ls\``    # CORRECT
    echo $(echo $(ls))    # CORRECT
  • Risolve un problema di comportamento incoerente quando si usano i backquotes:

    • echo '\$x' uscite \$x
    • echo `echo '\$x'` uscite $x
    • echo $(echo '\$x') uscite \$x
  • La sintassi Backticks ha restrizioni storiche sul contenuto del comando incorporato e non può gestire alcuni script validi che includono backquotes, mentre il $()modulo più recente può elaborare qualsiasi tipo di script incorporato valido.

    Ad esempio, questi script incorporati altrimenti validi non funzionano nella colonna di sinistra, ma funzionano sull'IEEE di destra :

    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 ')'
    `                              )

Pertanto la sintassi per la sostituzione del comando$ -prefisso dovrebbe essere il metodo preferito, poiché è visivamente chiaro con sintassi pulita (migliora la leggibilità umana e della macchina), è annidabile e intuitivo, l'analisi interna è separata ed è anche più coerente (con tutte le altre espansioni che vengono analizzate tra virgolette) in cui i backtick sono l'unica eccezione e il carattere viene facilmente mimetizzato quando è adiacente per renderlo ancora più difficile da leggere, specialmente con caratteri piccoli o insoliti.`"

Fonte: Perché si $(...)preferisce `...`(backtick)? a BashFAQ

Guarda anche:


1
Nidificato citando in accento grave in stile sostituzione è in realtà indefinito, è possibile utilizzare le virgolette doppie sia fuori o dentro , ma non entrambi, portabile. Le conchiglie le interpretano diversamente; alcuni richiedono una barra rovesciata per sfuggire a loro, altri richiedono che non vengano salvati con la barra rovesciata.
mirabilos,

23

Da man bash:

          $(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.

9

Oltre alle altre risposte,

$(...)

spicca visivamente meglio di

`...`

I bastoncini sembrano troppo apostrofi; questo varia a seconda del carattere che stai utilizzando.

(E, come ho appena notato, i backtick sono molto più difficili da inserire negli esempi di codice inline.)


2
devi avere una tastiera strana (o lo faccio io?). Per me, è molto più facile digitare i backtick: sono il tasto in alto a sinistra, non sono richiesti MAIUSC o ALT.
DVK,

1
@DVK: stavo parlando del loro aspetto, non della facilità di digitazione. (La mia tastiera è probabilmente la stessa della tua.) Tuttavia, ora che me lo dici, penso di avere una memoria muscolare migliore per $ (e di )quanto non faccia per il backtick; YMMV.
Keith Thompson,

mai programmato in bash (saltato dal vecchio ksh a Perl) quindi sicuramente nessuna memoria per quella sintassi specifica :)
DVK,

1
@DVK, pensavo che Keith si riferisse al fatto che il codice non-block qui (codice di blocco significa usare quattro spazi all'inizio della riga) usa i backtick per indicarlo, rendendo difficile inserire backtick in essi, un'altra illustrazione del difficoltà di nidificazione :-) FWIW, potresti trovare il codice e / i tag del codice (l'altro modo di fare codice non-block) possono contenere più facilmente i backtick.
paxdiablo,

@Pax - capito. Duh! Ero davvero bloccato mentalmente su codici a blocchi per qualche motivo.
DVK,

7

$() consente la nidificazione.

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

Penso che i backtick non lo permettano.


2
Puoi annidare i backtick; è molto più difficile: out=`echo today is \`date\`` .
Jonathan Leffler,

4

È lo standard POSIX che definisce la $(command)forma di sostituzione dei comandi. La maggior parte delle shell in uso oggi sono conformi a POSIX e supportano questa forma preferita rispetto alla notazione arcaica di backtick. La sezione di sostituzione dei comandi (2.6.3) del documento Shell Language descrive questo:

La sostituzione dei comandi consente di sostituire l'output di un comando al posto del nome del comando stesso. La sostituzione del comando deve avvenire quando il comando è racchiuso come segue:

$(command)

o (versione retroquotata):

`command`

La shell espande la sostituzione del comando eseguendo il comando in un ambiente subshell (consultare Shell Execution Environment ) e sostituendo la sostituzione del comando (il testo del comando più il "$ ()" o backquotes racchiuso) con l'output standard del comando, rimuovendo sequenze di uno o più <newline>caratteri alla fine della sostituzione. I <newline>caratteri incorporati prima della fine dell'output non devono essere rimossi; tuttavia, possono essere trattati come delimitatori di campo ed eliminati durante la divisione del campo, a seconda del valore di IFS e delle quotazioni in vigore. Se l'output contiene byte nulli, il comportamento non è specificato.

All'interno dello stile di sostituzione del comando retroquadrato, <backslash>deve conservare il suo significato letterale, tranne se seguito da: ' $', ' `' o <backslash>. La ricerca del backquote corrispondente deve essere soddisfatta dal primo backquote non quotato senza escape; durante questa ricerca, se si incontra un backquote senza escape all'interno di un commento della shell, un documento qui, una sostituzione di comando incorporata del modulo $ ( comando ) o una stringa tra virgolette, si verificano risultati indefiniti. Una stringa tra virgolette singole o doppie che inizia, ma non termina, all'interno della `...`sequenza " " produce risultati indefiniti.

Con il modulo $ ( comando ), tutti i caratteri che seguono la parentesi aperta alla parentesi di chiusura corrispondente costituiscono il comando . È possibile utilizzare qualsiasi script shell valido per il comando , ad eccezione di uno script costituito esclusivamente da reindirizzamenti che produce risultati non specificati.

I risultati della sostituzione dei comandi non devono essere elaborati per un'ulteriore espansione della tilde, espansione dei parametri, sostituzione dei comandi o espansione aritmetica. Se si verifica una sostituzione di comando tra virgolette doppie, la divisione dei campi e l'espansione del percorso non devono essere eseguite sui risultati della sostituzione.

La sostituzione dei comandi può essere nidificata. Per specificare l'annidamento all'interno della versione retroquotata, l'applicazione deve precedere i backquotes interni con <backslash>caratteri; per esempio:

\`command\`

La sintassi del linguaggio dei comandi della shell ha un'ambiguità per le espansioni che iniziano con "$((", che può introdurre un'espansione aritmetica o una sostituzione di comando che inizia con una subshell. L'espansione aritmetica ha la precedenza; vale a dire, la shell deve prima determinare se può analizzare l'espansione come espansione aritmetica e deve solo analizzare l'espansione come comando sostituzione se determina che non può analizzare l'espansione come espansione aritmetica. La shell non ha bisogno di valutare espansioni nidificate quando esegue questa determinazione. Se incontra la fine dell'input senza aver già determinato che non può analizzare l'espansione come espansione aritmetica, il shell considera l'espansione come un'espansione aritmetica incompleta e segnala un errore di sintassi. Un'applicazione conforme deve garantire che separa " $(" e "('in due token (ovvero, separarli con uno spazio bianco) in una sostituzione di comando che inizia con una subshell. Ad esempio, una sostituzione di comando contenente una singola subshell potrebbe essere scritta come:

$( (command) )


4

Questa è una domanda precedente, ma ho trovato un esempio perfettamente valido di $(...)over `...`.

Stavo usando un desktop remoto su Windows con Cygwin e volevo scorrere il risultato di un comando. Purtroppo, il personaggio di backtick era impossibile da inserire, a causa della cosa del desktop remoto o dello stesso cygwin.

È ragionevole presumere che un segno di dollaro e le parentesi saranno più facili da digitare in configurazioni così strane.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.