Le parentesi quadre doppie [[]] sono preferibili rispetto alle parentesi quadre singole [] in Bash?


574

Un collega ha recentemente affermato in una revisione del codice che il [[ ]]costrutto deve essere preferito rispetto [ ]a costrutti simili

if [ "`id -nu`" = "$someuser" ] ; then 
     echo "I love you madly, $someuser"
fi

Non poteva fornire una motivazione. Ce n'è uno?


19
Sii flessibile, a volte consenti a te stesso di ascoltare un consiglio senza richiedere una spiegazione approfondita :) Per quanto riguarda [[il codice, è buono e chiaro, ma ricorda quel giorno in cui porterai i tuoi script sul sistema con shell predefinita che non lo è basho ksh, ecc. [è più brutto, ingombrante, ma funziona come AK-47in ogni situazione.
Torre

178
@rook Puoi ascoltare un consiglio senza una spiegazione approfondita, certo. Ma quando richiedi una spiegazione e non la ottieni, di solito è una bandiera rossa. "Fidati, ma verifica" e tutto il resto.
Josip Rodin,


1
@rook in altre parole "fai quello che ti viene detto e non fare domande"
glifo

@rook Non aveva senso.
Rakib,

Risposte:


608

[[ha meno sorprese ed è generalmente più sicuro da usare. Ma non è portatile - POSIX non specifica cosa fa e solo alcune shell lo supportano (oltre a bash, ho sentito che anche ksh lo supporta). Ad esempio, puoi farlo

[[ -e $b ]]

per verificare se esiste un file. Ma con [, devi citare $b, perché divide l'argomento e espande cose come "a*"(dove lo [[prende alla lettera). Ciò ha anche a che fare con il modo in cui [può essere un programma esterno e riceve il suo argomento normalmente come qualsiasi altro programma (anche se può anche essere incorporato, ma non ha ancora questa gestione speciale).

[[ha anche alcune altre belle funzioni, come la corrispondenza delle espressioni regolari =~con gli operatori come sono conosciuti in linguaggi simili al C. Ecco una buona pagina a riguardo: Qual è la differenza tra test [e [[? e Bash Test


21
Considerando che bash è ovunque in questi giorni, tendo a pensare che sia dannatamente portatile. L'unica eccezione comune per me è sulle piattaforme busybox, ma probabilmente vorrai comunque fare uno sforzo speciale, dato l'hardware su cui gira.
pistole

14
@guns: Infatti. Suggerirei che la tua seconda frase smentisca la tua prima; se si considera lo sviluppo del software nel suo insieme, "bash è ovunque" e "l'eccezione è piattaforme trafficate" sono completamente incompatibili. busybox è molto diffuso per lo sviluppo integrato.
Razze di leggerezza in orbita

11
@guns: Anche se bash è installato sulla mia scatola, potrebbe non essere in esecuzione lo script (potrei avere / bin / sh collegato a qualche dash line, come standard su FreeBSD e Ubuntu). C'è anche una stranezza aggiuntiva con Busybox: con le opzioni di compilazione standard al giorno d'oggi, analizza [[ ]]ma lo interpreta come se fosse lo stesso [ ].
dubiousjim,

59
@dubiousjim: se usi costrutti solo bash (come questo), dovresti avere #! / bin / bash, non #! / bin / sh.
Nick Matteo

16
Questo è il motivo per cui utilizzo i miei script, #!/bin/shma poi li cambio da usare #!/bin/bashnon appena mi affido a qualche funzione specifica di BASH, per indicare che non è più portatile Bourne shell.
Anthony

151

Differenze comportamentali

Alcune differenze su Bash 4.3.11:

  • Estensione POSIX vs Bash:

  • comando regolare vs magia

    • [ è solo un comando regolare con un nome strano.

      ]è solo un argomento [che impedisce l'utilizzo di ulteriori argomenti.

      Ubuntu 16.04 in realtà ha un eseguibile per esso /usr/bin/[fornito da coreutils , ma la versione integrata di bash ha la precedenza.

      Nulla è alterato nel modo in cui Bash analizza il comando.

      In particolare, <è il reindirizzamento &&e ||concatena più comandi, ( )genera sottotitoli a meno che non sia evaso da \, e l'espansione delle parole avviene come al solito.

    • [[ X ]]è un singolo costrutto che consente di Xanalizzarlo magicamente. <, &&, ||E ()sono trattati in modo speciale, e le regole suddivisione in parole sono diverse.

      Ci sono anche ulteriori differenze come =e =~.

      In bashese: [è un comando integrato ed [[è una parola chiave: /ubuntu/445749/whats-the-difference-between-shell-builtin-and-shell-keyword

  • <

  • && e ||

    • [[ a = a && b = b ]]: vero, logico e
    • [ a = a && b = b ]: errore di sintassi, &&analizzato come separatore di comandi ANDcmd1 && cmd2
    • [ a = a -a b = b ]: equivalente, ma deprecato da POSIX³
    • [ a = a ] && [ b = b ]: POSIX ed equivalente affidabile
  • (

    • [[ (a = a || a = b) && a = b ]]: falso
    • [ ( a = a ) ]: errore di sintassi, ()viene interpretato come una subshell
    • [ \( a = a -o a = b \) -a a = b ]: equivalente, ma ()è deprecato da POSIX
    • { [ a = a ] || [ a = b ]; } && [ a = b ] Equivalente POSIX⁵
  • suddivisione delle parole e generazione del nome del file in caso di espansioni (split + glob)

    • x='a b'; [[ $x = 'a b' ]]: vero, virgolette non necessarie
    • x='a b'; [ $x = 'a b' ]: errore di sintassi, si espande in [ a b = 'a b' ]
    • x='*'; [ $x = 'a b' ]: errore di sintassi se c'è più di un file nella directory corrente.
    • x='a b'; [ "$x" = 'a b' ]: Equivalente POSIX
  • =

    • [[ ab = a? ]]: vero, perché esegue la corrispondenza dei modelli ( * ? [sono magici). Glob non si espande nei file nella directory corrente.
    • [ ab = a? ]: a?glob si espande. Quindi può essere vero o falso a seconda dei file nella directory corrente.
    • [ ab = a\? ]: false, non espansione globale
    • =e ==sono uguali in entrambi [e [[, ma ==è un'estensione di Bash.
    • case ab in (a?) echo match; esac: Equivalente POSIX
    • [[ ab =~ 'ab?' ]]: false⁴, perde la magia con ''
    • [[ ab? =~ 'ab?' ]]: vero
  • =~

    • [[ ab =~ ab? ]]: true, la corrispondenza delle espressioni regolari estese POSIX ?non si espande globalmente
    • [ a =~ a ]: Errore di sintassi. Nessun equivalente bash.
    • printf 'ab\n' | grep -Eq 'ab?': Equivalente POSIX (solo dati a linea singola)
    • awk 'BEGIN{exit !(ARGV[1] ~ ARGV[2])}' ab 'ab?': Equivalente POSIX.

Raccomandazione : usare sempre [].

Esistono equivalenti POSIX per ogni [[ ]]costrutto che ho visto.

Se ti usi [[ ]]:

  • perdere la portabilità
  • costringere il lettore a imparare le complessità di un'altra estensione bash. [è solo un comando regolare con un nome strano, nessuna semantica speciale è coinvolta.

¹ Ispirato al [[...]]costrutto equivalente nella shell Korn

² ma non riesce per alcuni valori di ao b(come +o index) e esegue un confronto numerico se ae bassomiglia a numeri decimali. expr "x$a" '<' "x$b"funziona intorno a entrambi.

³ e fallisce anche per alcuni valori di ao bcome !o (.

⁴ in bash 3.2 e versioni successive e la compatibilità fornita con bash 3.1 non è abilitata (come con BASH_COMPAT=3.1)

⁵ sebbene il raggruppamento (qui con il {...;}gruppo di comando anziché il (...)quale eseguirà una sottoshell non necessaria) non è necessario poiché gli operatori della shell ||e &&(al contrario degli operatori ||e && [[...]]o degli operatori -o/ -a [) hanno la stessa precedenza. Quindi [ a = a ] || [ a = b ] && [ a = b ]sarebbe equivalente.


4
Buone spiegazioni Uso [per gli stessi motivi.
Gordon,

2
In Bashese :) ah. Bel chiarimento delle distinzioni e dei motivi per rimanere con POSIX.
Jonathan Komar

56

[[ ]]ha più funzioni - Ti suggerisco di dare un'occhiata alla Guida agli script avanzati di Bash per maggiori informazioni, in particolare la sezione estesa del comando di prova nel Capitolo 7. Test .

Per inciso, come nota la guida, è [[ ]]stato introdotto in ksh88 (la versione del 1988 della shell Korn).


5
Questo è tutt'altro che un bashismo, è stato introdotto per la prima volta nella shell Korn.
Henk Langeveld,

6
@Thomas, l'ABS è in realtà considerato un riferimento molto scarso in molti ambienti; sebbene abbia una grande quantità di informazioni accurate, tende a prestare pochissima cura per evitare di mostrare cattive pratiche nei suoi esempi e ha trascorso gran parte della sua vita non mantenuta.
Charles Duffy,

@CharlesDuffy grazie per il tuo commento, puoi nominare una buona alternativa. Non sono un esperto di shell scripting, sto cercando una guida che posso consultare per scrivere una sceneggiatura una volta ogni sei mesi.
Thomas,

9
@Thomas, Wooledge BashFAQ e wiki associato sono ciò che uso; sono attivamente gestiti dagli abitanti del canale #bash di Freenode (che, sebbene a volte pungenti, tendono a preoccuparsi profondamente della correttezza). BashFAQ # 31, mywiki.wooledge.org/BashFAQ/031 , è la pagina direttamente pertinente a questa domanda.
Charles Duffy,

13

Da quale comparatore, test, parentesi o doppia parentesi è il più veloce? ( http://bashcurescancer.com )

La parentesi doppia è un "comando composto" in cui test e la parentesi singola sono incorporati nella shell (e in realtà sono lo stesso comando). Pertanto, la parentesi singola e la doppia parentesi eseguono codice diverso.

Il test e la parentesi singola sono i più portatili in quanto esistono come comandi separati ed esterni. Tuttavia, se si utilizza una versione remota di BASH, la doppia parentesi è supportata.


7
Cos'è l'ossessione del più veloce script di shell? Lo voglio più portatile e non potrebbe interessarmi di meno del miglioramento che [[potrebbe portare. Ma poi, sono una vecchia scoreggia di vecchia scuola :-)
Jens,

1
Penso che molte shell dimostrino le versioni integrate [e testanche se esistono anche versioni esterne.
dubiousjim

4
@Jens in generale sono d'accordo: l'intero scopo degli script è (era?) Portabilità (altrimenti, codificheremmo e compileremmo, non script) ... le due eccezioni che posso pensare sono: (1) completamento della scheda (dove gli script di completamento possono richiedere molto tempo con molta logica condizionale); e (2) super-prompt ( PS1=...crazy stuff...e / o $PROMPT_COMMAND); per questi, non voglio alcun ritardo percepibile nell'esecuzione della sceneggiatura.
michael

1
Parte dell'ossessione per il più veloce è semplicemente lo stile. A parità di altre condizioni, perché non incorporare il costrutto di codice più efficiente nel tuo stile predefinito, soprattutto se tale costrutto offre anche maggiore leggibilità? Per quanto riguarda la portabilità, molte attività per cui bash è adatto sono intrinsecamente non portatili. Ad esempio, devo correre apt-get updatese sono passate più di X ore dall'ultima corsa. È un grande sollievo quando si può lasciare la portabilità dall'elenco già troppo lungo di vincoli per il codice.
Ron Burk,

5

Se ti piace seguire la guida di stile di Google :

Test [e[[

[[ ... ]]riduce gli errori in quanto non si verifica l'espansione del nome del percorso o la suddivisione delle parole tra [[e ]]e [[ ... ]]consente la corrispondenza delle espressioni regolari dove [ ... ]non accade.

# This ensures the string on the left is made up of characters in the
# alnum character class followed by the string name.
# Note that the RHS should not be quoted here.
# For the gory details, see
# E14 at https://tiswww.case.edu/php/chet/bash/FAQ
if [[ "filename" =~ ^[[:alnum:]]+name ]]; then
  echo "Match"
fi

# This matches the exact pattern "f*" (Does not match in this case)
if [[ "filename" == "f*" ]]; then
  echo "Match"
fi

# This gives a "too many arguments" error as f* is expanded to the
# contents of the current directory
if [ "filename" == f* ]; then
  echo "Match"
fi

1
Google ha scritto " nessuna espansione del nome percorso ... ha luogo " ma [[ -d ~ ]]restituisce true (il che implica che è ~stato espanso /home/user). Penso che Google avrebbe dovuto essere più preciso nella sua scrittura.
JamesThomasMoon1979

2

Una situazione tipica in cui non è possibile utilizzare [[è in uno script configure.ac di autotools, qui le parentesi hanno un significato speciale e diverso, quindi dovrai usare testinvece di [o [[- Nota che test e [sono lo stesso programma.


2
Dato che gli autotools non sono una shell POSIX, perché mai ti aspetteresti [di essere definito come una funzione shell POSIX?
Gordon,

Poiché lo script autoconf sembra uno script di shell e produce uno script di shell e la maggior parte dei comandi di shell operano al suo interno.
vy32,

-1

[[]] le parentesi doppie non sono supportate in determinate versioni di SunOS e sono totalmente non dichiarate nelle dichiarazioni di funzioni di: GNU bash, versione 2.02.0 (1) -release (sparc-sun-solaris2.6)


1
molto vero e per nulla insignificante. bash portabilità tra le versioni precedenti. La gente dice "bash è onnipresente e portatile, tranne forse (inserire qui il sistema operativo esoterico) " - ma nella mia esperienza, solaris è una di quelle piattaforme in cui è necessario prestare particolare attenzione alla portabilità: non solo per considerare le versioni bash precedenti su un nuovo sistema operativo, problemi / bug con array, funzioni, ecc .; ma anche i programmi di utilità (usati negli script) come tr, sed, awk, tar hanno delle stranezze e peculiarità su Solaris che devi aggirare.
michael

2
hai ragione ... tante utility non POSIX su Solaris, basta guardare l'output "df" e gli argomenti ... Peccato per Sun. Spero che stia scomparendo a poco a poco (tranne che in Canada).
scavenger

7
Solaris 2.6 sul serio? È stato rilasciato nel 1997 e ha terminato il supporto nel 2006. Immagino che se lo stai ancora usando, allora hai altri problemi !. Per inciso ha usato Bash v2.02 che era quello che ha introdotto le doppie parentesi quindi dovrebbe funzionare anche su qualcosa di vecchio. Solaris 10 del 2005 ha utilizzato Bash 3.2.51 e Solaris 11 del 2011 utilizza Bash 4.1.11.
Pietro,

1
Ri Portabilità. Sui sistemi Linux c'è generalmente solo un'edizione di ogni strumento e questa è l'edizione GNU. Su Solaris in genere è possibile scegliere tra un'edizione nativa di Solaris o l'edizione GNU (ad esempio Solaris tar vs GNU tar). Se dipendi da estensioni specifiche di GNU, devi dichiararlo nel tuo script affinché sia ​​portatile. Su Solaris lo fai prefissando "g", ad esempio `ggrep` se vuoi GNU grep.
Pietro,

-2

In breve, [[è meglio perché non biforca un altro processo. Nessuna parentesi o una singola parentesi è più lenta di una doppia parentesi perché forgia un altro processo.


8
Test e [sono nomi per lo stesso comando incorporato in bash. Prova a usare type [per vedere questo.
AB,

1
@alberge, è vero, ma [[, come distinto da [, è la sintassi interpretata dall'interprete della riga di comando bash. In bash, prova a digitare type [[. unix4linux ha ragione nel dire che sebbene i classici [test Bourne-shell biforcano un nuovo processo per determinare il valore di verità, la [[sintassi (presa in prestito da ksh da bash, zsh, ecc.) no.
Tim Gilbert,

2
@Tim, non sono sicuro di quale shell Bourne stai parlando, ma [è integrata in Bash e Dash (la /bin/shdistribuzione di Linux derivata da Debian).
AB

1
Oh, capisco cosa intendi, è vero. Stavo pensando a qualcosa come, diciamo, / bin / sh su vecchi sistemi Solaris o HP / UX, ma ovviamente se avessi bisogno di essere compatibile con quelli che non avresti usato [[neanche.
Tim Gilbert,

1
La shell @alberge Bourne non è Bash (alias Bourne Again SHell ).
kiamlaluno,
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.