Citando entro $ (sostituzione comando) in Bash


126

Nel mio ambiente Bash uso variabili contenenti spazi e utilizzo queste variabili nella sostituzione dei comandi. Purtroppo non riesco a trovare la risposta su SE.

Qual è il modo corretto di citare le mie variabili? E come devo fare se questi sono nidificati?

DIRNAME=$(dirname "$FILE")

o cito al di fuori della sostituzione?

DIRNAME="$(dirname $FILE)"

o entrambi?

DIRNAME="$(dirname "$FILE")"

o uso i segni di spunta?

DIRNAME=`dirname "$FILE"`

Qual è il modo giusto per farlo? E come posso verificare facilmente se le virgolette sono impostate correttamente?




1
Questa è una buona domanda, ma dati tutti i problemi con gli spazi vuoti incorporati, perché dovresti rendere la vita difficile a te stesso usandoli di proposito?
Joe,

3
@Joe, con spazi vuoti incorporati intendi spazio nei nomi dei file? Personalmente non li uso così spesso, ma sto lavorando con directory e file di altre persone di cui non sono sicuro se contengano spazi. Inoltre, penso che sia meglio farlo subito, quindi non devo preoccuparmi in futuro.
Cugina Cocaina

4
Sì. Che cosa faremo con quelle "altre persone"? <G>
Joe

Risposte:


188

In ordine dal peggiore al migliore:

  • DIRNAME="$(dirname $FILE)" non farà quello che vuoi se $FILEcontiene spazi bianchi o personaggi sconvolgenti \[?*.
  • DIRNAME=`dirname "$FILE"`è tecnicamente corretto, ma i backtick non sono raccomandati per l'espansione dei comandi a causa della complessità aggiuntiva durante l'annidamento.
  • DIRNAME=$(dirname "$FILE")è corretto, ma solo perché si tratta di un compito . Se si utilizza la sostituzione del comando in qualsiasi altro contesto, come export DIRNAME=$(dirname "$FILE")o du $(dirname "$FILE"), la mancanza di virgolette causerà problemi se il risultato dell'espansione contiene spazi bianchi o caratteri globbing.
  • DIRNAME="$(dirname "$FILE")"è il modo raccomandato. È possibile sostituire DIRNAME=con un comando e uno spazio senza modificare nient'altro e dirnamericeve la stringa corretta.

Per migliorare ulteriormente:

  • DIRNAME="$(dirname -- "$FILE")"funziona se $FILEinizia con un trattino.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}"funziona anche se $FILEtermina con una nuova riga, poiché $()taglia le nuove righe alla fine dell'output e dirnamegenera una nuova riga dopo il risultato. Sheesh dirname, perché devi essere diverso?

Puoi annidare le espansioni di comando quanto vuoi. Con $()te crei sempre un nuovo contesto di quotazione, così puoi fare cose come questa:

foo "$(bar "$(baz "$(ban "bla")")")"

Tu non vuoi provare quello con apici inversi.


3
Risposta chiara alla mia domanda. Quando annido queste variabili, posso continuare a quotare come faccio ora?
Cugina Cocaina,

3
Esiste un riferimento / una risorsa che dettaglia il comportamento delle virgolette all'interno di una sottostazione di comando tra virgolette?
Amadeus Dr Zaius

12
@AmadeusDrZaius "Con $()te crei sempre un nuovo contesto di quotazione", quindi è come fuori dalle virgolette esterne. Non c'è nient'altro, per quanto ne so.
l0b0

4
@ l0b0 Grazie, sì, ho trovato la tua spiegazione molto chiara. Mi stavo solo chiedendo se fosse in un manuale anche da qualche parte. L'ho trovato (anche se ufficiosamente) a Wooledge . Immagino che se leggi attentamente l' ordine di sostituzione , potresti derivare questo fatto di conseguenza.
Amadeus Dr Zaius

1
Quindi le virgolette nidificate sono accettabili, ma ci eliminano perché la maggior parte degli schemi di colorazione della sintassi non rileva la circostanza speciale. Neat.
Luke Davis,

19

Puoi sempre mostrare gli effetti della quotazione delle variabili con printf.

Suddivisione delle parole eseguita su var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 citato, quindi nessuna suddivisione delle parole:

$ printf '[%s]\n' "$var1"
[hello     world]

Scissione di parole var1all'interno $(), equivalente a echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

Nessuna suddivisione in parole var1, nessun problema con la mancata citazione di $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

Ripartizione della parola var1:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Citando entrambi, il modo più semplice per essere sicuri.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Problema globale

Non quotare una variabile può anche portare all'espansione globale dei suoi contenuti:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Si noti che ciò si verifica solo dopo l'espansione della variabile. Non è necessario citare un glob durante l'assegnazione:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Utilizzare set -fper disabilitare questo comportamento:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

E set +fper riattivarlo:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]

8
Le persone tendono a dimenticare che la divisione delle parole non è l'unico problema, potresti voler cambiare il tuo esempio var1='hello * world'per illustrare anche il problema dei globbing.
Stéphane Chazelas,

10

Aggiunta alla risposta accettata:

Anche se in genere sono d'accordo con la risposta di @ l0b0 qui, sospetto che il posizionamento di backtick nudi nell'elenco "peggiore in assoluto " sia almeno in parte il risultato del presupposto che $(...)è disponibile ovunque. Mi rendo conto che la domanda specifica Bash, ma ci sono molte volte in cui Bash scopre che significa /bin/sh, che potrebbe non essere sempre la shell Bourne Again completa.

In particolare, la semplice shell Bourne non saprà cosa farsene $(...), quindi gli script che dichiarano di essere compatibili con essa (ad es. Tramite una #!/bin/shlinea shebang) probabilmente si comporteranno male se sono effettivamente gestiti dal "reale" /bin/sh- questo è di interesse speciale quando, per esempio, producendo script di inizializzazione o impacchettando pre e post-script, e può atterrare in un posto sorprendente durante l'installazione di un sistema di base.

Se qualcuno di questi sembra qualcosa che stai pianificando di fare con questa variabile, l'annidamento è probabilmente meno preoccupante che avere lo script in realtà, prevedibilmente eseguito. Quando è un caso abbastanza semplice e la portabilità è una preoccupazione, anche se mi aspetto che lo script di solito venga eseguito su sistemi in cui si /bin/shtrova Bash, spesso tendo ad usare i backtick per questo motivo, con assegnazioni multiple anziché nidificazione.

Detto questo, l'ubiquità delle shell che implementano $(...)(Bash, Dash, et al.), Ci lascia in un buon posto per rimanere con la sintassi POSIX più carina, più facile da annidare e più recentemente preferita nella maggior parte dei casi, per tutti i motivi menzionati da @ l0b0.

A parte: questo è apparso occasionalmente anche su StackOverflow -


//, risposta eccellente. In passato ho riscontrato problemi di compatibilità all'indietro anche con / bin / sh. Hai qualche consiglio su come affrontare il suo problema usando metodi compatibili con le versioni precedenti?
Nathan Basanese,

secondo la mia esperienza: a volte potresti aver bisogno di cambiare i backtick in parentesi in dollari racchiuse, e non una volta ha mai aiutato (stato necessario, per essere precisi) a cambiare le parentesi in dollari racchiuse in backtick. Scrivo solo backtick nel mio codice javascript e non nel codice shell.
Steven Lu
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.