Il test o [o [[è più portatile sia tra shell bash che tra altre shell?


44

Vedo che posso fare

$ [ -w /home/durrantm ] && echo "writable"
writable

o

$ test -w /home/durrantm && echo "writable"
writable

o

$ [[ -w /home/durrantm ]] && echo "writable"
writable

Mi piace usare la terza sintassi. Sono equivalenti in tutti i modi e per tutti i casi negativi e marginali? Ci sono differenze nella portabilità, ad esempio tra bash su Ubuntu e su OS X o versioni bash precedenti / precedenti, ad esempio prima / dopo 4.0 ed entrambi espandono le espressioni allo stesso modo?


2
Per [ … ]vs [[ … ]]vs test …, ci sono risposte più complete in questa domanda per lo più duplicata .
Gilles 'SO- smetti di essere malvagio'

Per il problema specifico del test per la scrivibilità di un file, vedere anche Come test non invasivo per l'accesso in scrittura a un file?
G-Man dice "Ripristina Monica" il

1
C'è un detto: "non esiste un codice portatile, solo un codice che è stato portato". Il mio consiglio a riguardo: Usa il modulo più leggibile (probabilmente [[...]]) e provalo su tutte le piattaforme che vuoi supportare. Non è molto utile oscurare i tuoi script in modo che vengano eseguiti su piattaforme antiche che né tu né il tuo pubblico target utilizza. Renderà il tuo codice difficile da leggere, introdurrà bug inutili e forse anche problemi di sicurezza (come ha fatto per openssl).
stefan.schwetschke,

Risposte:


31

[è sinonimo del testcomando ed è contemporaneamente un comando incorporato e un comando separato. Ma [[è una parola chiave bash e funziona solo in alcune versioni. Quindi, per motivi di portabilità, è meglio usare single []otest

[ -w "/home/durrantm" ] && echo "writable"

2
[è incorporato POSIX o Bash?
Sandburg,

@Sandburg POSIX è uno standard, quindi non può avere built-in. Non definisce se qualcosa debba essere incorporato o meno nell'interprete, proprio quello che deve fare. Queste regole si applicano ad entrambi i moduli teste [allo stesso modo. Se una shell può eseguire da sola la valutazione, ciò consente di risparmiare un processo e di renderlo un po 'più veloce, ma il risultato deve essere lo stesso.
Bachsau,

@Bachsau che ancora non risponde alla domanda di Sandburg sul fatto che il comportamento di [sia definito o meno da POSIX e quindi massimamente portabile (che sembra essere l'intero punto di questa domanda se non sbaglio)
JamesTheAwesomeDude

@Sandburg (e per chiunque altro si imbattono in questo): Sì, sembra entrambi [e test sono POSIX . Sembra che non ci sia né "raccomandato", anche se l'unica differenza (?) Che posso individuare tra loro è che test"non riconoscerà l'argomento" - "[come un delimitatore che indica la fine delle opzioni]" (? - implica che " - " è riconosciuto come fine degli argomenti per i [quali non riesco a trovare un buon caso di prova comunque. Ma sto divagando. Usa quello che vuoi. IMO, [sembra elegante, ma è testchiaramente un comando)
JamesTheAwesomeDude

35

Sì, ci sono differenze. I più portatili sono testo [ ]. Entrambi fanno parte delle specifiche POSIXtest .

Il if ... ficostrutto è anche definito da POSIX e dovrebbe essere completamente portatile.

Il [[ ]]è una kshcaratteristica che è presente anche in alcune versioni di bash( tutti quelli moderni ), in zshe forse in altri, ma non è presente nella sho dasho le varie altre shell più semplici.

Così, per rendere i vostri script portatile, usare [ ], testo if ... fi.


10
Solo per notare, bashe zshho supportato [[per molto tempo (l' bashho aggiunto alla fine degli anni '90, zshnon oltre il 2000, e sarei sorpreso se mancasse mai del supporto), quindi è improbabile che tu possa incontrare una versione di entrambi [[. L'incontro con una shell conforme a POSIX (come dash) è molto più probabile.
Chepner,

1
Una caratteristica interessante di [[è che le espansioni di parametri non devono essere quotate: l' [[-f $file]]evento funziona se $filecontiene caratteri di spazi bianchi.
helpermethod,

2
@helpermethod inoltre, incorporando espressioni regolari[[ $a =~ ^reg.*exp.*$' ]]
GnP,

20

Si noti che [] && cmdnon è lo stesso della if .. ficostruzione.

A volte il suo comportamento è abbastanza simile e puoi usarlo al [] && cmdposto di if .. fi. Ma solo a volte. Se hai più di un comando da eseguire se la condizione o hai bisogno di if .. else .. fistare attento e supplica la logica.

Un paio di esempi:

[ -z "$VAR" ] && ls file || echo wiiii

Non è lo stesso di

if [ -z $VAR ] ; then
  ls file
else
  echo wiii
fi

perché se lsfallirà, echoverrà eseguito che non accadrà if.

Un altro esempio:

[ -z "$VAR" ] && ls file && echo wiii

non è lo stesso di

if [ -z "$VAR" ] ; then
   ls file
   echo $wiii
fi

sebbene questa costruzione agirà allo stesso modo

[ -z "$VAR" ] && { ls file ; echo wiii ; }

si prega di notare che ;dopo l'eco è importante e deve essere lì.

Quindi, riprendendo la frase sopra possiamo dire

[] && cmd == se il primo comando ha esito positivo, eseguire il successivo

if .. fi == if condition (che potrebbe anche essere il comando test) quindi eseguire i comandi

Quindi solo per portabilità [e [[uso [.

ifè compatibile POSIX. Quindi, se devi scegliere tra [e ifscegliere guardando il tuo compito e il comportamento previsto.


Probabilmente sto solo diventando denso, ma non vedo quale sarebbe la differenza ... potresti per favore fare un esempio in cui quelle due costruzioni darebbero risultati diversi?
evilsoup,

1
@evilsoup, aggiornato. Forse non sono il miglior spiegatore, anche se spero che ora sia chiaro.
corsa il

1
Ottimi punti, fretta. Suppongo che una forma sicura del primo esempio potrebbe essere: [ -z "$VAR" ] && { ls file; true; } || echo wiiii. È un po 'più prolisso, ma è ancora più breve della if...ficostruzione.
PM 2Ring

Ha fatto in modo che la domanda riguardasse [vs [[vs test e non anche se ... fi vs && per renderla UNA domanda. Sfortunatamente farlo fa sembrare questa risposta fuori punto. Ci scusiamo per non aver inizialmente focalizzato la domanda su ciò che ha portato a questo. Vivi e (prova a) imparare. :)
Michael Durrant,

Ho annullato il voto perché a) questa è principalmente una buona risposta, ma è una risposta a una domanda diversa. b) presenti [e ifcome sottotitoli, ma non lo sono. In realtà è il &&che sta sostituendo il if. [esegue un po 'di codice e restituisce uno stato, molto simile lse grepvorrebbe. ifl'esecuzione dei rami dipende dallo stato di ritorno del comando (istruzione) dato dopo l'if, potrebbe essere qualsiasi comando (istruzione). &&esegue l'istruzione successiva solo se la precedente ha restituito 0, proprio come una semplice if..then..fi.
GnP,

9

In realtà è quello &&che sta sostituendo il if, non il test: ifun'istruzione nella shell scripting verifica se un comando ha restituito uno stato di uscita "riuscito" (zero); nel tuo esempio, il comando è [.

Quindi, in realtà ci sono due cose che stai variando qui: il comando utilizzato per eseguire il test e la sintassi utilizzata per eseguire il codice in base al risultato di quel test.

Comandi di test:

  • testè un comando standardizzato per la valutazione delle proprietà di stringhe e file; nel tuo esempio, stai eseguendo il comandotest -w /home/durrantm
  • [è un alias di quel comando, ugualmente standardizzato, che ha un ultimo argomento obbligatorio ]per apparire come un'espressione tra parentesi; non farti ingannare, è ancora solo un comando (potresti persino scoprire che il tuo sistema ha un file chiamato /bin/[)
  • [[è una versione estesa del comando test incorporato in alcune shell, ma non fa parte dello stesso standard POSIX; include opzioni extra che non stai utilizzando qui

Espressioni condizionali:

  • L' &&operatore ( qui standardizzato ) esegue un'operazione AND logica, valutando due comandi e restituendo 0 (che rappresenta vero) se entrambi restituiscono 0; valuterà il secondo comando solo se il primo ha restituito zero, quindi può essere usato come un semplice condizionale
  • Il if ... then ... ficostrutto ( standardizzato qui ) usa lo stesso metodo di giudicare la "verità", ma consente un elenco composto di affermazioni nella thenclausola, piuttosto che il singolo comando offerto da un &&cortocircuito, e fornisce elife elseclausole, che sono difficili da scrivere usando solo &&e ||. Si noti che non ci sono parentesi attorno alla condizione in ifun'istruzione .

Pertanto, i seguenti sono tutti rendering ugualmente portatili e del tutto equivalenti del tuo esempio:

  • test -w /home/durrantm && echo "writable"
  • [ -w /home/durrantm ] && echo "writable"
  • if test -w /home/durrantm; then echo "writable"; fi
  • if [ -w /home/durrantm ]; then echo "writable"; fi

Mentre i seguenti sono anche equivalenti, ma meno portatili a causa della natura non standard di [[:

  • [[ -w /home/durrantm ]] && echo "writable"
  • if [[ -w /home/durrantm ]]; then echo "writable"; fi

Sì, ho rimosso la parte if .... fi per far sì che questa sia 1 domanda.
Michael Durrant,

7

Per la portabilità, utilizzare test/ [. Ma se non hai bisogno della portabilità, per il bene della sanità mentale di te stesso e degli altri che leggi la tua sceneggiatura [[. :)

Vedi anche What is the difference between test, [ and [[ ?in BashFAQ .


7

Se vuoi la portabilità al di fuori del mondo alla Bourne, allora:

test -w /home/durrantm && echo writable

è il più portatile. Funziona nelle conchiglie del Bourne cshe nelle rcfamiglie.

test -w /home/durrantm && echo "writable"

output sarebbe "writable"invece writablein gusci della rcfamiglia ( rc, es, akanga, in cui "non è speciale).

[ -w /home/durrantm ] && echo writable

non funzionerebbe nelle shell delle famiglie csho rcsui sistemi in cui non è presente un [comando $PATH(alcuni sono noti per avere testma non il suo [alias).

if [ -w /home/durrantm ]; then echo writabe; fi

funziona solo con conchiglie della famiglia Bourne.

[[ -w /home/durrantm ]] && echo writable

funziona solo in ksh(dove ha avuto origine) zshe bash(tutti e 3 nella famiglia Bourne).

Nessuno funzionerebbe nella fishshell dove è necessario:

[ -w /home/durrantm ]; and echo writable

o:

if [ -w /home/durrantm ]; echo writable; end

0

La mia ragione più importante per scegliere uno if foo; then bar; fio foo && barè se lo stato di uscita dell'intero comando è importante.

confrontare:

#!/bin/sh
set -e
foo && bar
do_baz

con:

#!/bin/sh
set -e
if foo; then bar; fi
do_baz

Potresti pensare che facciano lo stesso; tuttavia se foofallisce (o è falso, a seconda del punto di vista), nel primo esempio do_baz non verrà eseguito, poiché lo script sarà uscito ... Il set -ecomando indica alla shell di uscire immediatamente se un comando restituisce un falso stato. Molto utile se stai facendo cose come:

cd /some/directory
rm -rf *

Non vuoi che lo script continui a funzionare se cdfallisce per qualsiasi motivo.


1
Nessun errore foonon interromperà lo script in entrambi i casi. Questo è un caso speciale per set -e(quando il comando viene valutato come condizione (a sinistra di alcune condizioni && / || o in condizioni if ​​/ while / until / elsif ...). In caso di errore bar, la shell uscirà in entrambi i casi.
Stéphane Chazelas,

2
Volete cd /some/directory && rm -rf -- *o cd /some/directory || exit; rm -rf -- *(non rimuovete ancora i file nascosti). Personalmente non mi piace l'idea di usare set -ecome scusa per non fare uno sforzo per scrivere il codice corretto.
Stéphane Chazelas,
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.