Qual è la differenza tra gli operatori di Bash [[vs [vs (vs ((??


247

Sono un po 'confuso su cosa fanno questi operatori in modo diverso quando usati in bash (parentesi quadre, parentesi quadre doppie e parentesi quadre).

[[ , [ , ( , ((

Ho visto la gente usarli su affermazioni come questa:

if [[condition]]

if [condition]

if ((condition))

if (condition)

4
Potresti voler dare un'occhiata a unix.stackexchange.com/questions/tagged/test first
cuonglm


3
@cuonglm Ironico perché quel link produce questa domanda come primo risultato. Paradosso!
Insane

5
Suppongo che leggere la documentazione non sia un'opzione?
Razze di leggerezza in orbita,

34
Le parentesi e le parentesi non sono così facili da cercare nella documentazione, e questo è tutto ciò che hai se non conosci i nomi di quelle caratteristiche.
ilkkachu,

Risposte:


259

In ifgenere appare una dichiarazione

if commands1
then
   commands2
else
   commands3
fi

La thenclausola viene eseguita se il codice di uscita commands1è zero. Se il codice di uscita è diverso da zero, elseviene eseguita la clausola. commands1può essere semplice o complesso. Può, ad esempio, essere una sequenza di uno o più pipeline separate da uno degli operatori ;, &, &&, o ||. Le ifcondizioni mostrate di seguito sono solo casi speciali di commands1:

  1. if [ condition ]

    Questo è il testcomando shell tradizionale . È disponibile su tutte le shell POSIX. Il comando test imposta un codice di uscita e l' ifistruzione si comporta di conseguenza. I test tipici sono se esiste un file o se un numero è uguale a un altro.

  2. if [[ condition ]]

    Si tratta di una nuova variante aggiornata sul testda ksh che bash e zsh anche il supporto. Questo testcomando imposta anche un codice di uscita e l' ifistruzione agisce di conseguenza. Tra le sue funzionalità estese, può verificare se una stringa corrisponde a un'espressione regolare.

  3. if ((condition))

    Un'altra estensione ksh supportata anche da bash e zsh . Questo esegue l'aritmetica. Come risultato dell'aritmetica, viene impostato un codice di uscita e l' ifistruzione si comporta di conseguenza. Restituisce un codice di uscita pari a zero (vero) se il risultato del calcolo aritmetico è diverso da zero. Ad esempio [[...]], questo modulo non è POSIX e quindi non portatile.

  4. if (command)

    Questo esegue il comando in una subshell. Al termine del comando, imposta un codice di uscita e l' ifistruzione si comporta di conseguenza.

    Un motivo tipico per usare una subshell come questo è di limitare gli effetti collaterali di commandse commandrichiesto assegnazioni di variabili o altri cambiamenti nell'ambiente della shell. Tali modifiche non rimangono dopo il completamento della subshell.

  5. if command

    il comando viene eseguito e l' ifistruzione agisce in base al suo codice di uscita.


24
Grazie per aver incluso la 5a opzione. Questa è la chiave per capire come funziona davvero ed è sorprendentemente sottoutilizzata.
pulcini,

4
Si noti che in [realtà è un binario, non un comando o un simbolo interno. Vive generalmente in /bin.
Julien R.,

8
@JulienR. in realtà [è integrato, così com'è test. Sono disponibili versioni binarie per motivi di compatibilità. Guarda help [e help test.
OldTimer

4
Vale la pena notare che while ((non è POSIX, $((ovvero l'espansione aritmetica è ed è facile confonderli. Spesso una soluzione alternativa consiste nell'utilizzare qualcosa di simile [ $((2+2)) -eq 4 ]a fare uso dell'aritmetica nelle dichiarazioni
conditinali

1
Vorrei poter votare questa risposta più di una volta. Spiegazione perfetta.
Anthony Gatlin

77
  • (…)le parentesi indicano una subshell . Quello che c'è dentro non è un'espressione come in molte altre lingue. È un elenco di comandi (proprio come parentesi esterne). Questi comandi vengono eseguiti in un sottoprocesso separato, quindi qualsiasi reindirizzamento, assegnazione, ecc. Eseguito tra parentesi non ha alcun effetto al di fuori delle parentesi.
    • Con un segno di dollaro in testa, $(…)c'è una sostituzione di comando : c'è un comando tra parentesi e l'output del comando è usato come parte della riga di comando (dopo ulteriori espansioni a meno che la sostituzione non sia tra virgolette doppie, ma questa è un'altra storia ) .
  • { … }le parentesi graffe sono come parentesi in quanto raggruppano i comandi, ma influenzano solo l'analisi, non il raggruppamento. Il programma x=2; { x=4; }; echo $xstampe 4, mentre x=2; (x=4); echo $xle stampe 2. (bretelle Pur essendo le parole chiave devono essere delimitate e si trovano in posizione di comando (da qui lo spazio dopo {e ;prima }), mentre tra parentesi non lo fanno. Questo è solo un capriccio sintassi.)
    • Con un segno di dollaro principale, ${VAR}è un'espansione dei parametri , che si espande al valore di una variabile, con possibili trasformazioni extra. La ksh93shell supporta anche ${ cmd;}come forma di sostituzione dei comandi che non genera una subshell.
  • ((…))le doppie parentesi racchiudono un'istruzione aritmetica , ovvero un calcolo su numeri interi, con una sintassi simile ad altri linguaggi di programmazione. Questa sintassi viene utilizzata principalmente per le assegnazioni e nei condizionali. Questo esiste solo in ksh / bash / zsh, non in semplice sh.
    • La stessa sintassi viene utilizzata nelle espressioni aritmetiche $((…)), che si espandono al valore intero dell'espressione.
  • [ … ]le parentesi singole racchiudono espressioni condizionali . Le espressioni condizionali si basano principalmente su operatori come -n "$variable"verificare se una variabile è vuota e -e "$file"verificare se esiste un file. Si noti che è necessario uno spazio attorno a ciascun operatore (ad esempio [ "$x" = "$y" ], non [ "$x"="$y" ]) e uno spazio o un carattere come ;sia all'interno che all'esterno delle parentesi (ad esempio [ -n "$foo" ], non [-n "$foo"]).
  • [[ … ]]le parentesi doppie sono una forma alternativa di espressioni condizionali in ksh / bash / zsh con alcune funzionalità aggiuntive, ad esempio puoi scrivere [[ -L $file && -f $file ]]per verificare se un file è un collegamento simbolico a un file normale mentre le parentesi singole richiedono [ -L "$file" ] && [ -f "$file" ]. Vedi Perché l'espansione dei parametri con spazi senza virgolette funziona tra parentesi doppie [[ma non parentesi singole [? per ulteriori informazioni su questo argomento.

Nella shell, ogni comando è un comando condizionale: ogni comando ha uno stato di ritorno che è 0 che indica il successo o un numero intero compreso tra 1 e 255 (e potenzialmente più in alcune shell) che indica l'errore. Il [ … ]comando (o [[ … ]]modulo di sintassi) è un comando particolare che può anche essere scritto test …e ha esito positivo quando esiste un file, o quando una stringa non è vuota, o quando un numero è più piccolo di un altro, ecc. Il ((…))modulo di sintassi ha successo quando un numero è diverso da zero. Ecco alcuni esempi di condizionali in uno script di shell:

  • Verifica se myfilecontiene la stringa hello:

    if grep -q hello myfile; then 
  • Se mydirè una directory, cambiarla e fare cose:

    if cd mydir; then
      echo "Creating mydir/myfile"
      echo 'some content' >myfile
    else
      echo >&2 "Fatal error. This script requires mydir to exist."
    fi
  • Verifica se esiste un file chiamato myfilenella directory corrente:

    if [ -e myfile ]; then 
  • Lo stesso, ma includendo anche collegamenti simbolici penzolanti:

    if [ -e myfile ] || [ -L myfile ]; then 
  • Verifica se il valore di x(che si presume sia numerico) è almeno 2, portabile:

    if [ "$x" -ge 2 ]; then 
  • Verifica se il valore di x(che si presume sia numerico) è almeno 2, in bash / ksh / zsh:

    if ((x >= 2)); then 

Nota che la parentesi singola supporta -ainvece di &&, quindi si può scrivere:, [ -L $file -a -f $file ]che è lo stesso numero di caratteri tra parentesi senza il extra [e ]...
Alexis Wilke

6
@AlexisWilke Gli operatori -ae -osono problematici perché possono portare ad analisi errate se alcuni degli operandi coinvolti sembrano operatori. Ecco perché non li menziono: hanno zero vantaggi e non funzionano sempre. E non scrivere mai espansioni variabili non quotate senza una buona ragione: [[ -L $file -a -f $file ]]va bene ma con parentesi singole è necessario [ -L "$file" -a -f "$file" ](il che è ok, ad esempio se $fileinizia sempre con /o ./).
Gilles,

Nota che è [[ -L $file && -f $file ]](no -acon la [[...]]variante).
Stéphane Chazelas,

18

Dalla documentazione di bash :

(list)l'elenco viene eseguito in un ambiente subshell (vedere AMBIENTE DI ESECUZIONE DEI COMANDI di seguito). Le assegnazioni di variabili e i comandi integrati che influiscono sull'ambiente della shell non rimangono attivi dopo il completamento del comando. Lo stato di ritorno è lo stato di uscita dell'elenco.

In altre parole, ti assicuri che qualunque cosa accada in 'list' (come a cd) non abbia alcun effetto al di fuori di (e ). L'unica cosa che fuoriuscirà è il codice di uscita dell'ultimo comando o con set -eil primo comando che genera un errore (diverso da alcuni come if, whileetc.)

((expression))L'espressione viene valutata in base alle regole descritte di seguito in VALUTAZIONE ARITMETICA. Se il valore dell'espressione è diverso da zero, lo stato di ritorno è 0; altrimenti lo stato di ritorno è 1. Questo è esattamente equivalente a lasciare "espressione".

Questa è un'estensione bash che ti permette di fare matematica. Questo è in qualche modo simile all'utilizzo exprsenza tutti i limiti di expr(come avere spazi ovunque, scappare *, ecc.)

[[ expression ]]Restituisce uno stato di 0 o 1 a seconda della valutazione dell'espressione condizionale. Le espressioni sono composte dalle primarie descritte di seguito in ESPRESSIONI CONDIZIONATE. La suddivisione delle parole e l'espansione del percorso non vengono eseguite sulle parole tra [[e]]; vengono eseguite l'espansione della tilde, l'espansione dei parametri e delle variabili, l'espansione aritmetica, la sostituzione dei comandi, la sostituzione del processo e la rimozione delle quote. Gli operatori condizionali come -f devono essere non quotati per essere riconosciuti come primari.

Se utilizzati con [[, gli operatori <e> ordinano lessicograficamente utilizzando le impostazioni internazionali correnti.

Questo offre un test avanzato per confrontare stringhe, numeri e file un po 'come le testofferte, ma più potente.

[ expr ]Restituisce uno stato 0 (vero) o 1 (falso) in base alla valutazione dell'espressione condizionale expr. Ogni operatore e oper e deve essere un argomento separato. Le espressioni sono composte dalle primarie sopra descritte in ESPRESSIONI CONDIZIONATE. test non accetta alcuna opzione, né accetta e ignora un argomento di - come indicante la fine delle opzioni.

[...]

Questo chiama test. In realtà, ai vecchi tempi, [era un collegamento simbolico a test. Funziona allo stesso modo e hai gli stessi limiti. Poiché un binario conosce il nome con cui è stato avviato, il programma di test può analizzare i parametri fino a quando non trova un parametro ]. Divertenti trucchi Unix.

Si noti che in caso di bash, [e testsono funzioni built-in (come detto in un commento), ma più o meno valgono le stesse limitazioni.


1
Anche se teste [ovviamente sono comandi integrati in Bash, ma è probabile che esista anche un binario esterno.
ilkkachu,

1
Il binario esterno per [non è un collegamento simbolico alla testmaggior parte dei sistemi moderni.
Casuale 832,

1
In qualche modo, trovo divertente che si preoccupino di creare due binari separati, che hanno entrambi esattamente ciò di cui hanno bisogno, invece di combinarli e aggiungere un paio di condizionali. Anche se in realtà strings /usr/bin/testmostra che ha anche il testo di aiuto, quindi non so cosa dire.
ilkkachu,

2
@ Random832 Ottengo il tuo punto di vista sulla logica GNU per evitare comportamenti arg0 inattesi, ma sui requisiti POSIX, non sarei così affermativo. Mentre il testcomando deve ovviamente esistere come comando autonomo basato su file dallo standard, nulla in esso afferma che la sua [variante debba essere implementata in questo modo. Ad esempio, Solaris 11 non fornisce alcun [eseguibile ma è comunque pienamente conforme agli standard POSIX
jlliagre,

2
(uscita 1) ha un effetto al di fuori delle parentesi.
Alexander

14

[ vs [[

Questa risposta coprirà il sottoinsieme [vs [[della domanda.

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: https://askubuntu.com/questions/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 ]POSIX equivalente 5
  • 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é corrisponde al modello ( * ? [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 4 , perde la magia con''
    • [[ ab? =~ 'ab?' ]]: vero
  • =~

    • [[ ab =~ ab? ]]: true, la corrispondenza delle espressioni regolari estese POSIX ?non si espande a livello globale
    • [ 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 fa un confronto numerico se ae bsembra numeri decimali. expr "x$a" '<' "x$b"funziona intorno a entrambi.

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

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

5 se il raggruppamento (qui con il {...;}gruppo di comando anziché (...)che sarebbe in una subshell inutile) non è necessaria in quanto i ||e &&operatori guscio (diversamente le ||e && [[...]]operatori oi -o/ -a [operatori) hanno uguale precedenza. Quindi [ a = a ] || [ a = b ] && [ a = b ]sarebbe equivalente.


@ StéphaneChazelas grazie per le informazioni! Ho aggiunto expralla risposta. Il termine "estensione Bash" non significa che Bash sia stata la prima shell ad aggiungere un po 'di sintassi, imparare POSIX sh vs Bash è già abbastanza per farmi impazzire.
Ciro Santilli 5 改造 中心 法轮功 六四 事件

Vedi man testse hai provato man [e ti sei perso. Questo spiegherà la variante POSIX.
Jonathan Komar il

13

Qualche esempio:

Test tradizionale:

foo="some thing"
# check if value of foo is not empty
if [ -n "$foo" ] ; then... 
if test -n "$foo" ; then... 

teste [sono comandi come tutti gli altri, quindi la variabile è divisa in parole a meno che non sia tra virgolette.

Test di nuovo stile

[[ ... ]] è un costrutto shell (più recente) speciale, che funziona in modo leggermente diverso, la cosa più ovvia è che non divide le parole:

if [[ -n $foo ]] ; then... 

Qualche documentazione su [e [[qui .

Test aritmetico:

foo=12 bar=3
if (( $foo + $bar == 15 )) ; then ...  

Comandi "normali":

Tutto quanto sopra si comporta come un normale comando e ifpuò assumere qualsiasi comando:

# grep returns true if it finds something
if grep pattern file ; then ...

Comandi multipli:

Oppure possiamo usare più comandi. Il wrapping di un set di comandi ( ... )li esegue in subshell, creando una copia temporanea dello stato della shell (directory di lavoro, variabili). Se è necessario eseguire temporaneamente un programma in un'altra directory:

# this will move to $somedir only for the duration of the subshell 
if ( cd $somedir ; some_test ) ; then ...

# while here, the rest of the script will see the new working
# directory, even after the test
if cd $somedir ; some_test ; then ...

1

Comandi di raggruppamento

Bash offre due modi per raggruppare un elenco di comandi da eseguire come unità.

( list )Posizionando un elenco di comandi tra parentesi si crea un ambiente subshell e ciascuno dei comandi nell'elenco viene eseguito in quella subshell. Poiché l'elenco viene eseguito in una subshell, le assegnazioni delle variabili non rimangono attive dopo il completamento della subshell.

$ a=1; (a=2; echo "inside: a=$a"); echo "outside: a=$a"
inside: a=2
outside: a=1

{ list; }Posizionare un elenco di comandi tra parentesi graffe fa sì che l'elenco venga eseguito nel contesto della shell corrente . Non viene creata alcuna subshell. È richiesto il seguente punto e virgola (o newline). fonte

${} Parameter expansion Ex:  ANIMAL=duck; echo One $ANIMAL, two ${ANIMAL}s
$() Command substitution Ex: result=$(COMMAND) 
$(()) Arithmetic expansion Ex: var=$(( 20 + 5 )) 

Costrutti condizionali

Staffa singola, ad esempio []
per il confronto ==, !=, <,e >dovrebbe essere usata e per il confronto numerico eq, ne,lte gtdovrebbe essere usata.

Staffe avanzate, ad es[[]]

In tutti gli esempi precedenti, abbiamo usato solo parentesi singole per racchiudere l'espressione condizionale, ma bash consente parentesi doppie che fungono da versione migliorata della sintassi della parentesi singola.

Per confronto ==, !=, <,e >può usare letteralmente.

  • [è sinonimo di comando test. Anche se è integrato nella shell, crea un nuovo processo.
  • [[ è una nuova versione migliorata, che è una parola chiave, non un programma.
  • [[è compreso da Korne Bash.

fonte

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.