2012-04-08 12 views
71

Qualcuno potrebbe dirmi se dovrei inserire virgolette sulle variabili in uno script di shell?Quando racchiudere tra virgolette una variabile di shell?

Ad esempio, è il seguente corretta:

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

o

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

E se sì, perché?

+10

Non ci sono domande stupide. Bene, ci sono _, ma questo non è uno di questi :-) – paxdiablo

+1

Vedere anche http://unix.stackexchange.com/questions/171346/security-implications-of-forgetting-to-quote-a-variable- shell in-bash-posix – tripleee

+0

Questa domanda ottiene molti duplicati, molti dei quali non riguardano variabili, quindi ho cambiato il nome in "valore" anziché in "variabile". Spero che questo aiuti più persone a trovare questo argomento. – tripleee

risposta

45

Regola generale: quotarlo se può essere vuoto o contenere spazi (o qualsiasi spazio effettivamente) o caratteri speciali (caratteri jolly). Non citare stringhe con spazi porta spesso alla shell a rompere un singolo argomento in molti.

$? non ha bisogno di virgolette poiché è un valore numerico. Se lo standard $URL dipende da ciò che si consente in là e se si desidera ancora un argomento se è vuoto.

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

+0

così dovresti alternare tra quoting e non-quoting variables nei tuoi script? grazie per la tua risposta – Cristian

+0

Penso che valga la pena aggiungere a questa risposta quale sarebbe l'effetto di non citare una variabile con spazi. – Owen

+0

devi solo citare variabili stringa? – Cristian

40

In breve, cita 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 adatto quando è necessario assicurarsi che la shell non tocchi affatto la stringa. In genere, è il meccanismo di quoting di scelta quando non si richiede 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 sono adatte quando è richiesta l'interpolazione variabile. Con adattamenti adatti, è anche una buona soluzione quando hai bisogno di virgolette singole nella stringa. (Non v'è alcun modo semplice per sfuggire a una sola citazione tra virgolette singole, perché non esiste un meccanismo di fuga all'interno virgolette singole - se ci fosse, non avrebbero citare testualmente completamente.)

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

Nessun citazioni sono adatti quando si richiede specificamente alla shell di eseguire la divisione dei token e/o l'espansione di caratteri jolly.

Scissione di 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 il singolo, stringa tra apici.)

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

(Il ciclo viene eseguito solo una volta, nel corso del singolo letterale - stringa quotata.)

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 v'è alcun file chiamato letteralmente file*.txt.)

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

(Non v'è alcun file chiamato $pattern, sia!)

In termini più concreti, tutto ciò che contiene il nome del file di solito dovrebbe essere citato (perché i nomi dei file possono contenere spazi e altri metacaratteri della shell). Qualunque cosa contenga un URL dovrebbe di solito essere quotata (perché molti URL contengono metacaratteri della shell come ? e &). Qualunque cosa contenga una regex dovrebbe di solito essere citata (idem come idem). Tutto ciò che contiene spazi bianchi significativi diversi da spazi singoli tra personaggi non bianchi deve essere citato (perché altrimenti, la shell munge gli spazi bianchi in spazi effettivamente, singoli, e taglia ogni spazio bianco iniziale o finale).

Quando si sa che una variabile può contenere solo un valore che non contiene metacaratteri della shell, la quotatura è facoltativa. Pertanto, un valore non quotato $? è fondamentalmente valido, poiché questa variabile può contenere solo un numero singolo. Tuttavia, "$?" è anche corretto e consigliato per coerenza generale e correttezza (sebbene questa sia la mia raccomandazione personale, non una politica ampiamente riconosciuta).

I valori che non sono variabili seguono fondamentalmente le stesse regole, anche se è possibile sfuggire ai metacaratteri anziché quotarli. Per un esempio comune, un URL con un & in esso sarà analizzato dalla shell come un comando di fondo a meno che il metacarattere sia sfuggito o citato:

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

(Naturalmente, questo accade anche se l'URL è in una variabile non quotata). Per una stringa statica, le virgolette singole hanno più senso, anche se qualsiasi forma di citazione o di escaping funziona qui.

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 è necessario combinare virgolette singole e doppie, è possibile utilizzarle adiacenti l'una all'altra. Ad esempio, le seguenti stringhe citato

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

può essere incollato insieme schiena contro schiena, formando un unico lunga stringa dopo tokenizzazione e citare la rimozione.

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

Questo non è terribilmente leggibile, ma è una tecnica comune e quindi utile da conoscere.

A parte, script should usually not use ls for anything. 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 nel secondo esempio;. printf funziona specificamente bene con argomenti multipli stat troppo Ma ciclo su carattere jolly è un problema comune, e spesso fatto in modo errato..)

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

+0

Questa è una variante di (parte di) una risposta che ho postato su una [domanda correlata] (http://stackoverflow.com/questions/25277037/printing-asterisk-in-bash-shell). Lo sto incollando qui perché questo è succinto e ben definito abbastanza da diventare una questione canonica per questo particolare problema. – tripleee

+0

Noterò che questo è l'elemento n. 0 e un tema ricorrente nella collezione http://mywiki.wooledge.org/BashPitfalls degli errori Bash comuni. Molti, molti dei singoli elementi in quella lista sono fondamentalmente su questo problema. – tripleee

4

Ecco una formula a tre punti per le citazioni in generale:

virgolette

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

virgolette singole

In stringhe letterali dove vogliamo sopprimere l'interpolazione di trattamenti speciali di backslash. In altre parole, le situazioni in cui l'utilizzo di virgolette doppie sarebbe inappropriato.

No cita

in contesti in cui siamo assolutamente sicuri che non ci siano la suddivisione delle parole o problemi globbing o noi vogliono suddivisione delle parole e globbing.


Esempi

virgolette

  • stringhe letterali con spazi bianchi ("StackOverflow rocks!", "Steve's Apple")
  • espansioni variabili ("$var", "${arr[@]}")
  • sostituzioni di comando ("$(ls)", "`ls`")
  • gocce dove percorso di directory o il nome del file di parte include spazi ("/my dir/"*)
  • per proteggere virgolette singole ("single'quote'delimited'string")
  • espansione di parametro Bash ("${filename##*/}")

Citazioni singole

  • i nomi dei comandi e gli argomenti che non hanno spazi bianchi in loro
  • stringhe letterali che devono interpolazione per essere soppressi ('Really costs $$!', 'just a backslash followed by a t: \t')
  • per proteggere le virgolette doppie ('The "crux"')
  • letterali regex che hanno bisogno di interpolazione per essere soppressa
  • utilizzare shell citando per letterali che coinvolgono i caratteri speciali ($'\n\t')
  • uso guscio citando in cui abbiamo bisogno di proteggere diverse citazioni singole e doppie ($'{"table": "users", "where": "first_name"=\'Steve\'}')

No cita

  • intorno variabili numeriche standard ($$, $?, $# etc.)
  • in contesti aritmetici come ((count++)), "${arr[idx]}", "${string:start:length}"
  • all'interno [[ ]] espressione che è libero dalla scissione parola e problemi globbing (questa è una questione di stile e opinioni possono variare ampiamente)
  • dove vogliamo suddivisione delle parole (for word in $words)
  • dove vogliamo globbing (for txtfile in *.txt; do ...)
  • dove vogliamo ~ debba essere interpretato nel $HOME (~/"some dir" ma non "~/some dir")

Consulta anche:

+3

Secondo queste linee guida, si otterrebbe un elenco di file nella directory radice scrivendo '" ls ""/"' La frase "tutti i contesti di stringhe" deve essere qualificata con più attenzione. –

+4

In '[[]]', il quoting ha importanza sul lato destro di '='/'==' e '= ~': fa la differenza tra interpretare una stringa come pattern/regex o letteralmente. –

+0

@WilliamPursell: '" ls ""/"' è, in effetti, equivalente a 'ls /'. – mklement0

0

Io generalmente uso citato come "$var" per sicuro, a meno che non sono sicuro che $var non contiene spazio.

Io faccio uso $var come un 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 
Problemi correlati