Errore nel test della parentesi shell quando la stringa è una parentesi sinistra


27

Ero fiducioso del fatto che quotare le stringhe fosse sempre una buona pratica per evitare che la shell la analizzasse.

Poi mi sono imbattuto in questo:

$ x='('
$ [ "$x" = '1' -a "$y" = '1' ]
bash: [: `)' expected, found 1

Cercando di isolare il problema, ottenendo lo stesso errore:

$ [ '(' = '1' -a '1' = '1' ]
bash: [: `)' expected, found 1

Ho risolto il problema in questo modo:

[ "$x" = '1' ] && [ "$y" = '1' ]

Devo ancora sapere cosa sta succedendo qui.


2
Come soluzione alternativa, in bash, puoi usare[[ "$x" = '1' && "$y" = '1' ]]
terdon

3
La specifica POSIX per il test descrive esplicitamente -ae -ocome obsoleta per questo motivo (che è ciò che significa l' [OB]apice accanto alla loro specifica). Se [ "$x" = 1 ] && [ "$y" = 1 ]invece scrivessi , staresti bene e rientrerebbe nel regno del comportamento ben definito / standardizzato.
Charles Duffy,

6
Questo è il motivo per cui le persone erano solite utilizzare [ "x$x" = "x1" ]per evitare che gli argomenti fossero interpretati erroneamente come operatori.
Jonathan Leffler,

@JonathanLeffler: Ehi, hai condensato la mia risposta a una sola frase, non è giusto! :) Se si utilizza una shell POSIX come dashanziché Bash, è comunque una pratica utile.
Animale nominale

Voglio ringraziare tutti coloro che hanno avuto il tempo di rispondere alla mia domanda, ragazzi davvero apprezzati :)! Voglio anche ringraziare per il montaggio essenziale fatto alla mia domanda. Entrare in questo forum a volte mi dà lo stesso brivido di fuggire da Alcatraz, mossa sbagliata significa la tua vita. Ad ogni modo, vorrei davvero che i miei ringraziamenti ti raggiungessero prima che questo commento venga cancellato
Claudio,

Risposte:


25

Questo è un caso molto oscuro che si potrebbe considerare un bug nel modo [in cui è definito il test integrato; tuttavia, corrisponde al comportamento del file [binario effettivo disponibile su molti sistemi. Per quanto posso dire, colpisce solo alcuni casi e una variabile che ha un valore che corrisponde a un [operatore come (, !, =, -e, e così via.

Lasciami spiegare perché e come aggirarlo nelle shell Bash e POSIX.


Spiegazione:

Considera quanto segue:

x="("
[ "$x" = "(" ] && echo yes || echo no

Nessun problema; quanto sopra non produce alcun errore e produce yes. Ecco come ci aspettiamo che le cose funzionino. '1'Se lo desideri, puoi modificare la stringa di confronto e il valore di x, e funzionerà come previsto.

Si noti che l'attuale /usr/bin/[binario si comporta allo stesso modo. Se si esegue ad es. Non si '/usr/bin/[' '(' = '(' ']'verifica alcun errore, poiché il programma è in grado di rilevare che gli argomenti consistono in una singola operazione di confronto di stringhe.

Il bug si verifica quando noi e con una seconda espressione. Non importa quale sia la seconda espressione, purché sia ​​valida. Per esempio,

[ '1' = '1' ] && echo yes || echo no

produce yesed è ovviamente un'espressione valida; ma, se combiniamo i due,

[ "$x" = "(" -a '1' = '1' ] && echo yes || echo no

Bash rifiuta l'espressione se e solo se xè (o !.

Se dovessimo eseguire quanto sopra usando il [programma vero e proprio

'/usr/bin/[' "$x" = "(" -a '1' = '1' ] && echo yes || echo no

l'errore sarebbe comprensibile: dal momento che la shell esegue le sostituzioni variabili, il /usr/bin/[binario riceve solo i parametri ( = ( -a 1 = 1e la terminazione ], comprensibilmente non riesce a capire se le parentesi aperte avviano o meno un'espressione secondaria, essendo coinvolta un'operazione e un'operazione. Certo, analizzarlo come due confronti di stringhe è possibile, ma farlo avidamente in quel modo potrebbe causare problemi quando applicato a espressioni appropriate con sottoespressioni tra parentesi.

Il problema, in realtà, è che la shell [integrata si comporta allo stesso modo, come se espandesse il valore di xprima di esaminare l'espressione.

(Queste ambiguità, e altre relative all'espansione variabile, sono state una delle ragioni principali per cui Bash ha implementato e ora consiglia invece di utilizzare le [[ ... ]]espressioni di test.)


La soluzione è banale e spesso si vede negli script che usano shshell più vecchie . Aggiungi un carattere "sicuro", spesso x, davanti alle stringhe (entrambi i valori vengono confrontati), per garantire che l'espressione sia riconosciuta come confronto di stringhe:

[ "x$x" = "x(" -a "x$y" = "x1" ]

1
Non definirei il comportamento del [built-in un bug. Semmai, è un difetto inerente al design. [[è una parola chiave della shell , non solo un comando integrato, quindi esamina le cose prima della rimozione delle virgolette e in realtà sostituisce la solita suddivisione delle parole. ad es. [[ $x == 1 ]]non è necessario utilizzarlo "$x", perché un [[contesto è diverso dal normale. Ad ogni modo, ecco come [[è possibile evitare le insidie ​​di [. POSIX richiede [di comportarsi in questo modo, e bash è per lo più conforme a POSIX anche senza --posix, quindi cambiare [in una parola chiave non è attraente.
Peter Cordes,

La soluzione alternativa non è consigliata da POSIX. Basta usare due chiamate a [.
Wildcard il

@PeterCordes: abbastanza giusto; buon punto. (Forse avrei dovuto usare progettato anziché definito nel mio primo paragrafo, ma [il comportamento è gravato dalla storia precedente a POSIX, ho scelto invece quest'ultima parola.) Evito personalmente di usare [[nei miei script di esempio, ma solo perché è un eccezione alla raccomandazione di citazione che mi curo sempre (poiché l'omissione delle citazioni è il motivo più comune per i bug di script che vedo) e non ho pensato a un semplice paragrafo per spiegare perché [[è un'eccezione alle regole, senza fare la mia raccomandazione di citazione sospettare.
Animale nominale

@Wildcard: No. POSIX non sconsiglia questa pratica , ed è quello che conta. Solo perché un'autorità non capita di raccomandare questa pratica, non rende questo male. In effetti, come faccio notare, si tratta di una pratica storica utilizzata negli shscript, che precede la standardizzazione POSIX. Utilizzando sottoespressioni tra parentesi e -ae -ooperatori logici nei test / [è più efficiente che basandosi sull'espressione concatenamento (via &&e ||); è solo che sulle macchine attuali, la differenza è irrilevante.
Animale nominale

@Wildcard: Tuttavia, preferisco personalmente concatenare le espressioni di test usando &&e ||, ma la ragione è che rende più facile per noi umani mantenerle (leggere, comprendere e modificarle se / quando necessario) senza introdurre bug. Quindi, non sto criticando il tuo suggerimento, ma solo il ragionamento alla base del suggerimento. (Per ragioni molto simili, il .LT., .GE., ecc operatori di confronto in FORTRAN 77 ha ottenuto versioni molto più umano-friendly <, >=ecc nelle versioni successive.)
nominale Animal

11

[aka testvede:

 argc: 1 2 3 4  5 6 7 8
 argv: ( = 1 -a 1 = 1 ]

testaccetta sottoespressioni tra parentesi; quindi pensa che la parentesi sinistra apra una sottoespressione e stia cercando di analizzarla; il parser vede =come prima cosa nella sottoespressione e pensa che sia un test implicito della lunghezza della stringa, quindi è felice; la sottoespressione dovrebbe quindi essere seguita da una parentesi destra e invece il parser trova 1invece ). E si lamenta.

Quando testha esattamente tre argomenti e l'argomento centrale è uno degli operatori riconosciuti, applica tale operatore al 1 ° e al 3 ° argomento senza cercare sottoespressioni tra parentesi.

Per i dettagli completi guarda man bash, cerca test expr.

Conclusione: l'algoritmo di analisi utilizzato da testè complicato. Utilizzare solo espressioni semplici e utilizzare le shell operatori !, &&e ||di combinarli.

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.