Quando racchiudere le virgolette attorno a una variabile shell?


184

Qualcuno potrebbe dirmi se dovrei avvolgere le virgolette intorno alle variabili in uno script di shell?

Ad esempio, è il seguente corretto:

xdg-open $URL 
[ $? -eq 2 ]

o

xdg-open "$URL"
[ "$?" -eq "2" ]

E se sì, perché?



Questa domanda riceve molti duplicati, molti dei quali non riguardano le variabili, quindi ho rinominato "valore" anziché "variabile". Spero che questo aiuti più persone a trovare questo argomento.
Tripleee,

1
@codeforester Che succede con la modifica ripristinata?
Tripleee


Risposte:


131

Regola generale: citala se può essere vuota o contenere spazi (o qualsiasi spazio bianco in realtà) o caratteri speciali (caratteri jolly). Non citare stringhe con spazi spesso porta alla shell che spezza un singolo argomento in molti.

$?non ha bisogno di virgolette poiché è un valore numerico. Se ne ha $URLbisogno dipende da cosa permetti lì dentro e se vuoi ancora un argomento se è vuoto.

Tendo a citare sempre le stringhe solo per abitudine poiché è più sicuro in quel modo.


2
Nota che "spazi" significa in realtà "qualsiasi spazio".
William Pursell,

4
@Cristian: se non sei sicuro di ciò che potrebbe essere nella variabile, è più sicuro citarlo. Tendo a seguire lo stesso principio di paxdiablo e ho l'abitudine di citare tutto (a meno che non ci sia un motivo specifico per non farlo).
Gordon Davisson,

11
Se non conosci il valore di IFS, citalo in ogni caso. Se IFS=0, allora echo $?può essere molto sorprendente.
Charles Duffy,

3
Cita in base al contesto, non a quello che ti aspetti dai valori, altrimenti i tuoi bug peggioreranno. Per esempio, si è sicuri che nessuno dei vostri percorsi hanno spazi, così pensi di poter scrivere cp $source1 $source2 $dest, ma se per qualche motivo imprevisto destnon viene impostato, il terzo argomento appena scompare, e sarà in silenzio copiare source1sopra source2invece di dare voi un errore appropriato per la destinazione vuota (come sarebbe se si fosse citato ogni argomento).
Derek Veit,

3
quote it if...ha il processo di pensiero all'indietro - le virgolette non sono qualcosa che aggiungi quando è necessario, sono qualcosa che rimuovi quando è necessario. Avvolgere sempre stringhe e script tra apici a meno che non dovete usare le virgolette (ad esempio, per lasciare una variabile espandere) o necessità di utilizzare senza virgolette (ad esempio, per fare globbing e il nome del file di espansione).
Ed Morton,

92

In breve, citare tutto ciò in cui non è necessario che la shell esegua la divisione dei token e l'espansione dei caratteri jolly.

Le virgolette singole proteggono il testo tra di loro alla lettera. È lo strumento adeguato quando è necessario assicurarsi che la shell non tocchi affatto la stringa. In genere, è il meccanismo di quotazione preferito quando non è richiesta l'interpolazione variabile.

$ echo 'Nothing \t in here $will change'
Nothing \t in here $will change

$ grep -F '@&$*!!' file /dev/null
file:I can't get this @&$*!! quoting right.

Le virgolette doppie sono adatte quando è richiesta l'interpolazione variabile. Con adattamenti adeguati, è anche una buona soluzione quando hai bisogno di virgolette singole nella stringa. (Non esiste un modo semplice per sfuggire a una singola virgoletta tra virgolette singole, perché non esiste un meccanismo di escape all'interno di virgolette singole - se esistesse, non citerebbero completamente alla lettera.)

$ echo "There is no place like '$HOME'"
There is no place like '/home/me'

Nessuna virgoletta è adatta quando si richiede specificamente alla shell di eseguire la suddivisione dei token e / o l'espansione dei caratteri jolly.

Divisione del token;

 $ words="foo bar baz"
 $ for word in $words; do
 >   echo "$word"
 > done
 foo
 bar
 baz

Al contrario:

 $ for word in "$words"; do echo "$word"; done
 foo bar baz

(Il ciclo viene eseguito solo una volta, sopra la singola stringa tra virgolette.)

 $ for word in '$words'; do echo "$word"; done
 $words

(Il ciclo viene eseguito solo una volta, sopra la stringa letterale a virgoletta singola.)

Espansione jolly:

$ pattern='file*.txt'
$ ls $pattern
file1.txt      file_other.txt

Al contrario:

$ ls "$pattern"
ls: cannot access file*.txt: No such file or directory

(Non esiste alcun file chiamato letteralmente file*.txt.)

$ ls '$pattern'
ls: cannot access $pattern: No such file or directory

(Non c'è nemmeno un file chiamato $pattern!)

In termini più concreti, tutto ciò che contiene un nome di file dovrebbe di solito essere citato (poiché i nomi di file possono contenere spazi bianchi e altri metacaratteri della shell). Qualunque cosa contenga un URL dovrebbe di solito essere quotata (perché molti URL contengono metacaratteri come ?e &). Tutto ciò che contiene un regex dovrebbe essere di solito citato (idem idem). Tutto ciò che contiene spazi bianchi significativi diversi dai singoli spazi tra caratteri non bianchi deve essere citato (perché altrimenti, la shell immergerà lo spazio bianco in, efficacemente, spazi singoli e taglierà qualsiasi spazio bianco iniziale o finale).

Quando sai che una variabile può contenere solo un valore che non contiene metacaratteri di shell, la quotazione è facoltativa. Pertanto, un non quotato $?va sostanzialmente bene, perché questa variabile può sempre e solo contenere un solo numero. Tuttavia, "$?"è anche corretto e raccomandato per coerenza e correttezza generali (sebbene questa sia la mia raccomandazione personale, non una politica ampiamente riconosciuta).

I valori che non sono variabili seguono sostanzialmente le stesse regole, anche se potresti quindi sfuggire ai metacaratteri invece di citarli. Per un esempio comune, un URL con un &al suo interno verrà analizzato dalla shell come comando in background a meno che il metacarattere non sia salvato o citato:

$ wget http://example.com/q&uack
[1] wget http://example.com/q
-bash: uack: command not found

(Naturalmente, ciò accade anche se l'URL si trova in una variabile non quotata.) Per una stringa statica, le virgolette singole hanno più senso, anche se qui funziona qualsiasi forma di quotazione o escape.

wget 'http://example.com/q&uack'  # Single quotes preferred for a static string
wget "http://example.com/q&uack"  # Double quotes work here, too (no $ or ` in the value)
wget http://example.com/q\&uack   # Backslash escape
wget http://example.com/q'&'uack  # Only the metacharacter really needs quoting

L'ultimo esempio suggerisce anche un altro concetto utile, che mi piace chiamare "quotazione altalena". Se devi mescolare virgolette singole e doppie, puoi usarle una accanto all'altra. Ad esempio, le seguenti stringhe tra virgolette

'$HOME '
"isn't"
' where `<3'
"' is."

può essere incollato insieme schiena contro schiena, formando una singola stringa lunga dopo la tokenizzazione e la rimozione delle quotazioni.

$ echo '$HOME '"isn't"' where `<3'"' is."
$HOME isn't where `<3' is.

Questo non è terribilmente leggibile, ma è una tecnica comune e quindi buono a sapersi.

A parte questo, gli script di solito non dovrebbero essere usati lsper nulla. Per espandere un carattere jolly, basta ... usarlo.

$ printf '%s\n' $pattern   # not ``ls -1 $pattern''
file1.txt
file_other.txt

$ for file in $pattern; do  # definitely, definitely not ``for file in $(ls $pattern)''
>  printf 'Found file: %s\n' "$file"
> done
Found file: file1.txt
Found file: file_other.txt

(Il ciclo è completamente superfluo in quest'ultimo esempio; in printfparticolare funziona bene con più argomenti.stat Anche. Il ciclo su una corrispondenza jolly è un problema comune e spesso eseguito in modo errato.)

Una variabile contenente un elenco di token su cui eseguire il loop o un carattere jolly da espandere viene vista meno frequentemente, quindi a volte abbreviamo per "citare tutto a meno che tu non sappia esattamente cosa stai facendo".


1
Questa è una variante di (parte di) una risposta che ho inviato a una domanda correlata . Lo sto incollando qui perché questo è abbastanza breve e ben definito da diventare una domanda canonica per questo particolare problema.
Tripleee,

4
Noterò che questo è l'articolo # 0 e un tema ricorrente nella raccolta mywiki.wooledge.org/BashPitfalls di errori Bash comuni. Molti, molti dei singoli elementi in quell'elenco riguardano sostanzialmente questo problema.
triplo il

27

Ecco una formula in tre punti per le virgolette in generale:

Virgolette

In contesti in cui vogliamo sopprimere la divisione delle parole e il globbing. Anche in contesti in cui vogliamo che il letterale sia trattato come una stringa, non una regex.

Virgolette singole

In letterali a stringhe dove vogliamo sopprimere l'interpolazione e il trattamento speciale delle barre rovesciate. In altre parole, le situazioni in cui l'uso di virgolette doppie sarebbe inappropriata.

Senza virgolette

In contesti in cui siamo assolutamente sicuri che non ci siano problemi di divisione o di frantumazione delle parole o che desideriamo dividere e frantumare le parole .


Esempi

Virgolette

  • stringhe letterali con spazi bianchi ( "StackOverflow rocks!", "Steve's Apple")
  • espansioni variabili ( "$var", "${arr[@]}")
  • sostituzioni comandi ( "$(ls)", "`ls`")
  • globs in cui il percorso della directory o la parte del nome file include spazi ( "/my dir/"*)
  • per proteggere le virgolette singole ( "single'quote'delimited'string")
  • Espansione dei parametri di Bash ( "${filename##*/}")

Virgolette singole

  • nomi di comandi e argomenti che contengono spazi bianchi
  • stringhe letterali che richiedono l'interpolazione da eliminare ( 'Really costs $$!', 'just a backslash followed by a t: \t')
  • per proteggere le doppie virgolette ( 'The "crux"')
  • letterali regex che richiedono l'interpolazione per essere soppressi
  • usare le citazioni di shell per i letterali che coinvolgono caratteri speciali ( $'\n\t')
  • utilizzare le citazioni della shell dove è necessario proteggere diverse virgolette singole e doppie ( $'{"table": "users", "where": "first_name"=\'Steve\'}')

Senza virgolette

  • variabili numeriche intorno normali ( $$, $?, $#etc.)
  • in contesti aritmetici piace ((count++)), "${arr[idx]}","${string:start:length}"
  • [[ ]]espressione interna che è libera da problemi di frazionamento e frantumazione di parole (questa è una questione di stile e le opinioni possono variare ampiamente)
  • dove vogliamo dividere le parole ( for word in $words)
  • dove vogliamo globbing ( for txtfile in *.txt; do ...)
  • dove vogliamo ~essere interpretati come $HOME( ~/"some dir"ma non "~/some dir")

Guarda anche:


3
Secondo queste linee guida, si otterrebbe un elenco di file nella directory principale scrivendo "ls" "/" La frase "tutti i contesti di stringa" deve essere qualificata con più attenzione.
William Pursell,

5
In [[ ]], la citazione è importante sul lato destro di =/ ==e =~: fa la differenza tra l'interpretazione di una stringa come modello / regex o letteralmente.
Benjamin W.

6
Una buona panoramica, ma vale la pena integrare i commenti di @ BenjaminW. e le stringhe con virgolette ANSI ( $'...') dovrebbero avere la loro sezione.
mklement0,

3
@ mklement0, in effetti sono equivalenti. Queste linee guida indicano che dovresti sempre digitare al "ls" "/"posto del più comune ls /, e lo prendo come un grosso difetto nelle linee guida.
William Pursell,

4
Per le virgolette potresti aggiungere un'assegnazione variabile o case:)
PesaIl

4

In genere uso citato come "$var"per sicurezza, a meno che non sia sicuro che $varnon contenga spazio.

Uso $varun modo semplice per unire le linee:

lines="`cat multi-lines-text-file.txt`"
echo "$lines"                             ## multiple lines
echo $lines                               ## all spaces (including newlines) are zapped

Il commento finale è in qualche modo fuorviante; le nuove righe vengono effettivamente sostituite con spazi, non semplicemente rimosse.
Tripleee

-1

Per utilizzare le variabili nello script della shell, utilizzare "" le variabili tra virgolette come quella tra virgolette significa che la variabile può contenere spazi o caratteri speciali che non influiranno sull'esecuzione dello script della shell. Altrimenti se sei sicuro di non avere spazi o caratteri speciali nel nome della tua variabile, puoi usarli senza "".

Esempio:

echo "$ url name" - (Può essere usato in ogni momento)

echo "$ url name" - (Non può essere usato in tali situazioni quindi prendi precauzioni prima di usarlo)

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.