Error en la prueba de shell shell cuando la cadena es un paréntesis izquierdo

27

Solía ​​confiar en el hecho de que citar cadenas siempre es una buena práctica para evitar que el shell lo analice.

Entonces me encontré con esto:

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

Intentando aislar el problema, obteniendo el mismo error:

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

Resolví el problema así:

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

Todavía necesito saber qué está pasando aquí.

Claudio
fuente
2
Como solución alternativa, en bash, puede usar[[ "$x" = '1' && "$y" = '1' ]]
terdon
3
La especificación POSIX para prueba describe explícitamente -ay -oes obsoleta por este motivo (que es lo que significa el [OB]superíndice al lado de su especificación). Si escribiste en su [ "$x" = 1 ] && [ "$y" = 1 ]lugar, estarías bien y estarías dentro del ámbito del comportamiento bien definido / estandarizado.
Charles Duffy
66
Es por eso que la gente solía usar [ "x$x" = "x1" ]para evitar que los argumentos se malinterpreten como operadores.
Jonathan Leffler
@ JonathanLeffler: ¡Hey, condensaste mi respuesta en una sola oración, no es justo! :) Si uno usa un shell POSIX como en dashlugar de Bash, sigue siendo una práctica útil.
Nominal Animal
Quiero agradecer a todos los que se tomaron el tiempo para responder a mi pregunta, ¡realmente apreciados chicos :)! También quiero agradecer por la edición vital hecha a mi pregunta. Entrar en este foro a veces me da la misma emoción de escapar de Alcatraz, un movimiento incorrecto significa tu vida. De todos modos Realmente deseo que mis Gracias alcanzarán antes de este comentario se elimina
Claudio

Respuestas:

25

Este es un caso de esquina muy oscuro que uno podría considerar un error en cómo [se define la prueba incorporada; sin embargo, coincide con el comportamiento del [binario real disponible en muchos sistemas. Por lo que yo puedo decir, que sólo afecta a ciertos casos y una variable que tiene un valor que coincide con un [operador como (, !, =, -e, y así sucesivamente.

Permítanme explicar por qué y cómo solucionarlo en los shells Bash y POSIX.


Explicación:

Considera lo siguiente:

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

No hay problema; lo anterior no produce ningún error, y las salidas yes. Así es como esperamos que las cosas funcionen. Puede cambiar la cadena de comparación a '1'si lo desea, y el valor de x, y funcionará como se espera.

Tenga en cuenta que el /usr/bin/[binario real se comporta de la misma manera. Si ejecuta, por ejemplo, '/usr/bin/[' '(' = '(' ']'no hay error, porque el programa puede detectar que los argumentos consisten en una sola operación de comparación de cadenas.

El error ocurre cuando nosotros y con una segunda expresión. No importa cuál sea la segunda expresión, siempre que sea válida. Por ejemplo,

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

salidas yes, y obviamente es una expresión válida; pero, si combinamos los dos,

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

Bash rechaza la expresión si y solo si xes (o !.

Si tuviéramos que ejecutar lo anterior utilizando el [programa real , es decir

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

el error sería comprensible: dado que el shell realiza las sustituciones variables, el /usr/bin/[binario solo recibe parámetros ( = ( -a 1 = 1y la terminación ], es comprensible que no pueda analizar si los paréntesis abiertos comienzan una sub-expresión o no, habiendo un y la operación en cuestión. Claro, analizarlo como dos comparaciones de cadenas es posible, pero hacerlo con avidez de esa manera puede causar problemas cuando se aplica a expresiones adecuadas con subexpresiones entre paréntesis.

El problema, en realidad, es que el shell [incorporado se comporta de la misma manera, como si expandiera el valor de xantes de examinar la expresión.

(Estas ambigüedades, y otras relacionadas con la expansión de variables, fueron una razón importante por la que Bash implementó y ahora recomienda usar las [[ ... ]]expresiones de prueba en su lugar).


La solución es trivial, y a menudo se ve en scripts que usan shshells más antiguos . Agrega un carácter "seguro", a menudo x, delante de las cadenas (se comparan ambos valores), para garantizar que la expresión se reconozca como una comparación de cadenas:

[ "x$x" = "x(" -a "x$y" = "x1" ]
Animal nominal
fuente
1
No llamaría al comportamiento del [error incorporado. En todo caso, es un defecto de diseño inherente. [[es una palabra clave de shell , no solo un comando incorporado, por lo que puede ver las cosas antes de eliminar las comillas y, de hecho, anula la división de palabras habitual. por ejemplo [[ $x == 1 ]], no necesita usar "$x", porque un [[contexto es diferente de lo normal. De todos modos, así es como [[se pueden evitar las trampas [. POSIX requiere [que se comporte de la manera que lo hace, y bash es en su mayoría compatible con POSIX incluso sin él --posix, por lo que cambiar [a una palabra clave no es atractivo.
Peter Cordes
POSIX no recomienda su solución alternativa. Solo usa dos llamadas a [.
Comodín el
@PeterCordes: Muy bien; buen punto. (Tal vez debería haber usado diseñado en lugar de definido en mi primer párrafo, pero [el comportamiento está cargado por la historia anterior a POSIX, elegí la última palabra en su lugar.) Personalmente evito usar [[en mis guiones de ejemplo, pero solo porque es un excepción a la recomendación de citas que siempre insisto (dado que omitir citas es la razón más común para los errores de script que veo), y no he pensado en un párrafo simple para explicar por qué [[es una excepción a las reglas, sin hacer mi recomendación de cita sospechar.
Nominal Animal
@Wildcard: No. POSIX no recomienda contra esta práctica , y eso es lo que importa. El hecho de que una autoridad no recomiende esta práctica no hace que esto sea malo. De hecho, como señalo, esta es una práctica histórica utilizada en los shscripts, que es anterior a la estandarización POSIX. Usando subexpresiones entre paréntesis y -ay -ooperadores lógicos en las pruebas / [es más eficiente que el confiar en el encadenamiento de la expresión (a través de &&y ||); Es solo que en las máquinas actuales, la diferencia es irrelevante.
Nominal Animal
@Wildcard: Sin embargo, personalmente prefiero encadenar las expresiones de prueba usando &&y ||, pero la razón es que nos facilita a los humanos mantenerlas (leerlas, comprenderlas y modificarlas si es necesario) sin introducir errores. Por lo tanto, no estoy criticando su sugerencia, sino solo el razonamiento detrás de la sugerencia. (Por razones muy similares, la .LT., .GE., etc. operadores de comparación en Fortran 77 tiene versiones mucho más humanos ambiente <, >=, etc., en las versiones posteriores.)
nominal Animal
11

[aka testve:

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

testacepta subexpresiones entre paréntesis; entonces piensa que el paréntesis izquierdo abre una subexpresión y está tratando de analizarlo; el analizador ve =como lo primero en la subexpresión y piensa que es una prueba de longitud de cadena implícita, por lo que es feliz; la subexpresión debería ir seguida de un paréntesis derecho y, en su lugar, el analizador encuentra en 1lugar de ). Y se queja.

Cuando testtiene exactamente tres argumentos, y el argumento del medio es uno de los operadores reconocidos, aplica ese operador a los argumentos primero y tercero sin buscar subexpresiones entre paréntesis.

Para ver todos los detalles man bash, busque test expr.

Conclusión: el algoritmo de análisis utilizado por testes complicado. Use solo expresiones simples y use los operadores de shell! , &&y ||para combinarlos.

AlexP
fuente