Perché l'espansione dei parametri con spazi senza virgolette funziona tra parentesi doppie “[[” ma non all'interno di parentesi singole “[”?


86

Sono confuso con l'utilizzo di parentesi singole o doppie. Guarda questo codice:

dir="/home/mazimi/VirtualBox VMs"

if [[ -d ${dir} ]]; then
    echo "yep"
fi

Funziona perfettamente anche se la stringa contiene uno spazio. Ma quando lo cambio in parentesi singola:

dir="/home/mazimi/VirtualBox VMs"

if [ -d ${dir} ]; then
    echo "yep"
fi

Dice:

./script.sh: line 5: [: /home/mazimi/VirtualBox: binary operator expected

Quando lo cambio in:

dir="/home/mazimi/VirtualBox VMs"

if [ -d "${dir}" ]; then
    echo "yep"
fi

Funziona bene Qualcuno può spiegare cosa sta succedendo? Quando devo assegnare virgolette doppie attorno a variabili come "${var}"per prevenire problemi causati da spazi?



Risposte:


83

La parentesi singola [è in realtà un alias per il testcomando, non è sintassi.

Uno degli aspetti negativi (di molti) della parentesi singola è che se uno o più degli operandi che sta tentando di valutare restituisce una stringa vuota, si lamenterà che si aspettava due operandi (binari). Questo è il motivo per cui vedi le persone fare [ x$foo = x$blah ], le xgaranzie che l'operando non valuterà mai una stringa vuota.

La parentesi doppia [[ ]], d'altra parte, è sintassi ed è molto più capace di [ ]. Come hai scoperto, non ha il problema con un singolo operando e consente anche una maggiore sintassi di tipo C con gli >, <, >=, <=, !=, ==, &&, ||operatori.

La mia raccomandazione è la seguente: Se il tuo interprete lo è #!/bin/bash, usa sempre[[ ]]

È importante notare che [[ ]]non è supportato da tutte le shell POSIX, tuttavia molte shell lo supportano come zshe kshin aggiunta abash


16
Il problema con la stringa vuota (e molti altri) viene risolto usando le virgolette. La "x" serve a coprire un altro tipo di problemi: dove gli operandi possono essere considerati operatori . Come quando $fooè !o (o -n... Quel problema non è pensato per essere un problema con le shell POSIX in cui il numero di argomenti (accanto [e ]) non è maggiore di quattro.
Stéphane Chazelas,

21
Chiarire, [ x$foo = x$blah ]è altrettanto sbagliato [ $foo = $bar ]. [ "$foo" = "$bar" ]è corretto in qualsiasi shell conforme a POSIX, [ "x$foo" = "x$bar" ]funzionerebbe in qualsiasi shell tipo Bourne, ma xnon è per i casi in cui si hanno stringhe vuote ma dove $foopossono essere !o -n...
Stéphane Chazelas

1
Semantica interessante in questa risposta. Non sono sicuro di cosa intendi per sintassi * non * . Certo, [non è proprio un alias per test. Se lo fosse, allora testaccetterebbe una parentesi quadra di chiusura. test -n foo ]. Ma testnon lo fa e [richiede uno. [è identico a testtutti gli altri aspetti, ma non è così che aliasfunziona. Bash descrive [e testcome builtin della shell , ma [[come parola chiave della shell .
Kojiro,

1
Bene, in bash [è un shell incorporato, ma / usr / bin / [è anche un eseguibile. È tradizionalmente un collegamento a / usr / bin / test, ma nei moderni gnu coreutils è un binario separato. La versione tradizionale del test esamina argv [0] per vedere se viene invocato come [e quindi cerca la corrispondenza].
Evan

My bash non funziona con >=e<=
Student

52

Il [comando è un comando normale. Sebbene la maggior parte delle shell lo fornisca come integrato per l'efficienza, obbedisce alle normali regole sintattiche della shell. [è esattamente equivalente a test, tranne per il fatto che [richiede a ]come ultimo argomento e testnon lo fa.

Le parentesi doppie [[ … ]]sono una sintassi speciale. Sono stati introdotti in ksh (diversi anni dopo [) perché [possono essere fastidiosi da usare correttamente e [[consentono alcune nuove belle aggiunte che usano caratteri speciali della shell. Ad esempio, puoi scrivere

[[ $x = foo && $y = bar ]]

perché l'intera espressione condizionale è analizzata dalla shell, mentre [ $x = foo && $y = bar ]sarebbe prima divisa in due comandi [ $x = fooe $y = bar ]separata &&dall'operatore. Allo stesso modo le doppie parentesi consentono cose come la sintassi di corrispondenza del modello, ad esempio [[ $x == a* ]]per verificare se il valore di xinizia con a; tra parentesi singole questo si espanderebbe a*all'elenco dei file i cui nomi iniziano con anella directory corrente. Le parentesi doppie sono state introdotte per la prima volta in ksh e sono disponibili solo in ksh, bash e zsh.

All'interno di parentesi singole, è necessario utilizzare virgolette doppie intorno alle sostituzioni variabili, come nella maggior parte degli altri posti, perché sono solo argomenti di un comando (che sembra essere il [comando). All'interno di doppie parentesi, non hai bisogno di doppie virgolette, perché la shell non fa divisione di parole o globbing: analizza un'espressione condizionale, non un comando.

Un'eccezione, tuttavia, è quella in [[ $var1 = "$var2" ]]cui sono necessarie le virgolette se si desidera eseguire un confronto tra stringhe byte-byte, altrimenti $var2sarebbe un modello con cui $var1confrontarsi.

Una cosa che non puoi fare [[ … ]]è usare una variabile come operatore. Ad esempio, questo è perfettamente legale (ma raramente utile):

if [ -n "$reverse_sort" ]; then op=-gt; else op=-lt; fi

if [ "$x" "$op" "$y" ]; then 

Nel tuo esempio

dir="/home/mazimi/VirtualBox VMs"
if [ -d ${dir} ]; then 

il comando all'interno ifsia [con i 4 argomenti -d, /home/mazimi/VirtualBox, VMse ]. La shell analizza -d /home/mazimi/VirtualBoxe quindi non sa cosa fare VMs. Dovresti impedire la divisione delle parole ${dir}per ottenere un comando ben formato.

In generale, usa sempre le doppie virgolette attorno alle sostituzioni di variabili e comandi a meno che tu non sappia che vuoi eseguire la divisione delle parole e il globbing sul risultato. I luoghi principali in cui è sicuro non usare le doppie virgolette sono:

  • in un compito: foo=$bar(ma tieni presente che hai bisogno delle doppie virgolette in export "foo=$bar"o in compiti di array come array=("$a" "$b"));
  • in una casedichiarazione case $foo in …:;
  • tra doppie parentesi tranne sul lato destro della =o ==dell'operatore (a meno che non si vuole pattern matching): [[ $x = "$y" ]].

In tutti questi, è corretto usare le virgolette doppie, quindi potresti anche saltare le regole avanzate e usare le virgolette continuamente.


1
@StephaneChazelas Un mio errore. Si scopre che [proviene effettivamente da System III nel 1981 , pensavo fosse più vecchio.
Gilles

8

Quando devo assegnare virgolette doppie attorno a variabili come "${var}"per prevenire problemi causati da spazi?

È implicito in questa domanda

Perché non è abbastanza buono?${variable_name}

${variable_name} non significa cosa pensi che faccia ...

... se pensi che abbia qualcosa a che fare con i problemi causati dagli spazi (in valori variabili). è buono per questo:${variable_name}

$ bar=foo
$ bard=Shakespeare
$ echo $bard
Shakespeare
$ echo ${bar}d
food

e nient'altro! 1  non fa nulla di buono a meno che non lo seguiate immediatamente con un carattere che potrebbe far parte di un nome di variabile: una lettera (-o-), un trattino basso () o una cifra (-). E anche allora, puoi aggirarlo:${variable_name}AZaz_09

$ echo "$bar"d
food

Non sto cercando di scoraggiarne l'uso - echo "${bar}d"è probabilmente la soluzione migliore qui - ma scoraggiare le persone dall'affidarsi a parentesi graffe anziché a virgolette, o applicando istintivamente parentesi graffe e poi chiedendo: “Ora, ho bisogno anche di virgolette ? "  Dovresti sempre usare le virgolette a meno che tu non abbia una buona ragione per non farlo e sei sicuro di sapere cosa stai facendo.
_________________
1    Tranne, ovviamente, per il fatto che le forme più fantasiose di espansione dei parametri , ad esempio, e si basano sulla sintassi. Inoltre, devi usare , ecc., Per fare riferimento al decimo, undicesimo, ecc., Parametri posizionali - le virgolette non ti aiuteranno in questo.${parameter:-[word]}${parameter%[word]}${parameter}${10}${11}


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.