¿Por qué es `[` un shell incorporado y `[[` una palabra clave de shell?

64

Hasta donde yo sé, [[es una versión mejorada de [, pero estoy confundido cuando lo veo [[como una palabra clave y [se muestra como incorporado.

[root@server ~]# type [
[ is a shell builtin
[root@server ~]# type [[
[[ is a shell keyword

TLDP dice

Un incorporado puede ser sinónimo de un comando del sistema con el mismo nombre, pero Bash lo reimplementa internamente. Por ejemplo, el comando Bash echo no es lo mismo que / bin / echo, aunque su comportamiento es casi idéntico.

y

Una palabra clave es una palabra reservada, token u operador. Las palabras clave tienen un significado especial para el shell, y de hecho son los componentes básicos de la sintaxis del shell. Como ejemplos, para, while, do y! son palabras clave De forma similar a un builtin, una palabra clave está codificada en Bash, pero a diferencia de un builtin, una palabra clave no es en sí misma un comando, sino una subunidad de una construcción de comando. [2]

¿No debería eso hacer ambos [y [[una palabra clave? ¿Hay algo que me falta aquí? Además, este enlace reafirma que ambos [y [[deben pertenecer al mismo tipo.

Sree
fuente
2
Ver unix.stackexchange.com/a/56674
Stéphane Chazelas
99
/ bin / [existe en mi máquina.
Joshua
2
Como una simple demostración de una diferencia entre los dos: if "[" $x -eq 3 ]funciona como se espera (porque Bash busca el comando llamado [, y esto existe), pero if "[[" $x -eq 3 ]]no no funciona (porque una vez más Bash busca de un comando del nombre apropiado, pero no hay [[mando).
Kyle Strand
1
@Joshua Sí /usr/bin/echo, pero eso no significa que no sea una construcción .
Jonathon Reinhart
Todos los bulitins que también existen externamente analizan argumentos si no fueron incorporados.
Joshua

Respuestas:

80

La diferencia entre [y [[es bastante fundamental.

  • [es un comando Sus argumentos se procesan tal como se procesan los argumentos de cualquier otro comando. Por ejemplo, considere:

    [ -z $name ]

    El shell se expandirá $namey realizará tanto la división de palabras como la generación de nombre de archivo en el resultado, tal como lo haría con cualquier otro comando.

    Como ejemplo, lo siguiente fallará:

    $ name="here and there"
    $ [ -n $name ] && echo not empty
    bash: [: too many arguments

    Para que esto funcione correctamente, se necesitan citas:

    $ [ -n "$name" ] && echo not empty
    not empty
  • [[es una palabra clave de shell y sus argumentos se procesan de acuerdo con reglas especiales. Por ejemplo, considere:

    [[ -z $name ]]

    La cáscara se expandirá $name, pero, a diferencia de cualquier otro comando, se llevará a cabo ni la división de palabras, ni generar nombres de archivos en el resultado. Por ejemplo, lo siguiente tendrá éxito a pesar de los espacios incrustados en name:

    $ name="here and there"
    $ [[ -n $name ]] && echo not empty
    not empty

Resumen

[ es un comando y está sujeto a las mismas reglas que todos los demás comandos que ejecuta el shell.

[[Sin embargo, debido a que es una palabra clave, no un comando, el shell lo trata especialmente y opera bajo reglas muy diferentes.

John1024
fuente
+1. Gracias. ¿Podría dar la fuente (referencia) de bajo qué reglas operan un comando y una palabra clave?
Tim
@Tim Las reglas bajo las cuales operan los comandos se detallan en man bash. Véanse, en particular, las secciones tituladas "EXPANSIÓN DE MANDO SIMPLE" y "EJECUCIÓN DE MANDO". Además de [[, otros de bash palabras clave incluyen if, then, while, y el 'caso'. No hay reglas generales para las palabras clave: cada palabra clave es un caso especial. man bash incluye detalles para cada uno.
John1024
62

En V7 , [se llamó a Unix , donde debutó el shell Bourne test, y existió solo como /bin/test. Entonces, el código que escribirías hoy como:

if [ "$foo" = "bar" ] ; then ...

hubieras escrito en su lugar como

if test "$foo" = "bar" ; then ...

Esta segunda notación todavía existe, y encuentro que está más claro lo que está sucediendo: está llamando a un comando llamado test, que evalúa sus argumentos y devuelve un código de estado de salida que se ifusa para decidir qué hacer a continuación. Ese comando puede estar integrado en el shell, o puede ser un programa externo.

[como alternativa a la que testvino después.² Puede ser un sinónimo incorporado para test, pero también se proporciona como /bin/[en los sistemas modernos para proyectiles que no lo tienen como incorporado.

[y testpuede implementarse usando el mismo código. Este es el caso para /bin/[y /bin/testen OS X, donde estos son enlaces duros al mismo ejecutable.³ Como resultado, la implementación ignora por completo el seguimiento ]: no lo requiere si lo llama como /bin/[, y no se queja si lo proporciona a /bin/test.⁴

Nada de esa historia afecta [[, porque nunca hubo un programa primordial llamado [[. Existe únicamente dentro de esos shells que lo implementan como una extensión del shell POSIX .

Parte de la distinción entre "incorporado" y "palabra clave" se debe a este historial. También refleja el hecho de que las reglas de sintaxis para analizar [[expresiones son diferentes, como se señala en la respuesta de John1024.


Notas al pie:

  1. Cuando lo mira de esa manera, deja en claro por qué debe colocar espacios [en los guiones de shell, a diferencia de la forma en que funcionan los paréntesis y corchetes en la mayoría de los otros lenguajes de programación. Si el analizador de comandos del shell permitiera if["$x"..., también tendría que permitiriftest"$x"...

  2. Sucedió alrededor de 1980. /bin/[no existe en mi copia de Ancient Unix V7 de 1979, ni lo man testdocumenta como un alias. En la entrada correspondiente a la página del manual que tengo en una copia preliminar del manual del Sistema III de 1980, está en la lista.

  3. ls -i /bin/[ /bin/test

  4. Pero no cuente con este comportamiento. La versión integrada de Bash [requiere el cierre ], y su base de testaplicación se quejará si no la proporcione.

  5. La distinción entre comandos internos y externos también puede ser importante por otra razón: las dos implementaciones pueden comportarse de manera diferente. Este es el caso echoen muchos sistemas. Debido a que solo hay una implementación, no es necesario hacer tal distinción para una palabra clave.

Warren Young
fuente
Gracias @Warren joven, pero ¿por qué builtiny keyworddistinción entre [y [[cuando tanto proporciona la misma funcionalidad (excepto el hecho de que [[viene con más características que [)?
Sree
2
@sree [[es una palabra clave que le permite a bash hacer cosas que no son posibles [, por ejemplo, no es necesario citar mucho tiempo, porque el shell sabe que es una variable. Es decir, el procesamiento de la línea de comando se ve afectado cuando se usa una palabra clave, pero no cuando se usa una incorporada, eso sucede mucho más tarde.
muru
1
Tenga en cuenta que el código para el shell Bourne de V7 muestra un [código incorporado, pero el código está comentado.
Stéphane Chazelas
cdes un programa incorporado, pero no oculta nada ( cdno se puede implementar como un programa externo).
Paŭlo Ebermann
2
Este es un excelente resumen histórico, pero deja de lado los detalles cruciales (OMI), señalados por muru arriba y por John1024 en su respuesta, que al hacer [[una palabra clave le permite al shell usar reglas de análisis especiales para sus argumentos. Por lo tanto, por desgracia, mi voto a favor va a John1024.
Ilmari Karonen
3

[originalmente era solo un comando externo, otro nombre para /bin/test. Pero algunos comandos, como [y echo, se usan con tanta frecuencia en los scripts de shell que los implementadores del shell decidieron copiar el código directamente en el mismo shell, en lugar de tener que ejecutar otro proceso cada vez que se usan. Eso convirtió estos comandos en "incorporados", aunque aún puede invocar el programa externo a través de su ruta completa.

[[Llegó mucho más tarde. Aunque el builtin se implementa internamente dentro del shell, se analiza como los comandos externos. Como se explica en la respuesta de John1024, esto significa que las variables sin comillas se dividirán en palabras, y los tokens como >y <se procesan como redirección de E / S. Esto hizo que escribir expresiones de comparación complejas fuera inconveniente. [[fue creado como sintaxis de shell, para que pueda analizarse ideosincráticamente. Dentro de las [[variables no se dividen las palabras, <y >pueden usarse como operadores de comparación, =pueden comportarse de manera diferente dependiendo de si se cita o no el siguiente parámetro, etc. Estas son todas las comodidades que hacen que sea [[más fácil de usar que el [comando / incorporado tradicional .

No podían simplemente recodificar una [sintaxis como esta porque habría sido un cambio incompatible con millones de scripts. Al usar la nueva [[sintaxis, que no existía anteriormente, podrían renovar totalmente la forma en que se usa de una manera compatible hacia arriba.

Esto es similar a la evolución que resultó en la $((...))sintaxis de las expresiones aritméticas, que reemplazó principalmente al exprcomando tradicional .

Barmar
fuente
0

El más nuevo [[en bashes una optimización de [.

El clásico [tiene un gran inconveniente, cuando se usa con frecuencia para hacer una operación trivial: generará un nuevo proceso cada vez:
(¡Crea un nuevo espacio de direcciones solo para comparar 0y 1siempre!)

Creo que un punto principal de la adición de [[fue hacer que la evaluación de la expresión dentro [no generara un proceso adicional. Pero cómo [funciona no podría cambiarse: crearía mucha confusión y problemas. Entonces, la optimización se implementó con un nuevo nombre, de una manera más eficiente, es decir, un comando integrado de shell.
Se convirtió en palabra clave en la sintaxis de shell como efecto secundario.

En el momento en que [se usó primero, era la forma correcta de hacerlo con un proceso externo.

Volker Siegel
fuente
55
Tenga en cuenta que, aunque [originalmente era un comando externo, se agregó como una función integrada en el shell bastante pronto, probablemente para cuando se lanzó Unix System III y definitivamente antes de que se lanzara Unix System V. Por lo tanto, el "proceso adicional" no ha sido un problema durante años. Sin embargo, la sintaxis se mantuvo sin cambios: se trató como si fuera un comando externo.
Jonathan Leffler
@JonathanLeffler Oh, gracias, extrañé que eso [sea ​​a la vez, eso significa algunos cambios ...
Volker Siegel