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.