¿Cuál es la diferencia entre shell incorporado y palabra clave de shell?


Respuestas:


45

Hay una gran diferencia entre una palabra clave y una incorporada, en la forma en que Bash analiza su código. Antes de hablar sobre la diferencia, enumeremos todas las palabras clave y las siguientes:

Construidos:

$ compgen -b
.         :         [         alias     bg        bind      break     
builtin   caller    cd        command   compgen   complete  compopt   
continue  declare   dirs      disown    echo      enable    eval      
exec      exit      export    false     fc        fg        getopts   
hash      help      history   jobs      kill      let       local     
logout    mapfile   popd      printf    pushd     pwd       read      
readarray readonly  return    set       shift     shopt     source    
suspend   test      times     trap      true      type      typeset   
ulimit    umask     unalias   unset     wait                          

Palabras clave:

$ compgen -k
if        then      else      elif      fi        case      
esac      for       select    while     until     do        
done      in        function  time      {         }         
!         [[        ]]        coproc              

Tenga en cuenta que, por ejemplo, [es un incorporado y que [[es una palabra clave. Usaré estos dos para ilustrar la diferencia a continuación, ya que son operadores bien conocidos: todos los conocen y los usan regularmente (o deberían).

Bash escanea y comprende una palabra clave muy temprano en su análisis. Esto permite, por ejemplo, lo siguiente:

string_with_spaces='some spaces here'
if [[ -n $string_with_spaces ]]; then
    echo "The string is non-empty"
fi

Esto funciona bien y Bash saldrá feliz

The string is non-empty

Tenga en cuenta que no cité $string_with_spaces. Mientras que lo siguiente:

string_with_spaces='some spaces here'
if [ -n $string_with_spaces ]; then
    echo "The string is non-empty"
fi

muestra que Bash no está contento:

bash: [: too many arguments

¿Por qué funciona con palabras clave y no con builtins? porque cuando Bash analiza el código, ve [[cuál es una palabra clave y comprende muy pronto que es especial. Por lo tanto, buscará el cierre ]]y tratará el interior de una manera especial. Un builtin (o comando) se trata como un comando real que se llamará con argumentos. En este último ejemplo, bash entiende que debe ejecutar el comando [con argumentos (se muestra uno por línea):

-n
some
spaces
here
]

ya que se produce la expansión variable, la eliminación de comillas, la expansión del nombre de ruta y la división de palabras. El comando [resulta estar construido en el shell, por lo que lo ejecuta con estos argumentos, lo que resulta en un error, de ahí la queja.

En la práctica, verá que esta distinción permite un comportamiento sofisticado, que no sería posible con los builtins (o comandos).

Todavía en la práctica, ¿cómo puedes distinguir una palabra clave de una palabra clave? Este es un experimento divertido de realizar:

$ a='['
$ $a -d . ]
$ echo $?
0

Cuando Bash analiza la línea $a -d . ], no ve nada especial (es decir, sin alias, sin redireccionamientos, sin palabras clave), por lo que solo realiza una expansión variable. Después de expansiones variables, ve:

[ -d . ]

por lo que se ejecuta el comando (incorporado) [con argumentos -d, .y ], que, por supuesto, es verdad (esto sólo comprueba si. es un directorio).

Ahora mira:

$ a='[['
$ $a -d . ]]
bash: [[: command not found

Oh. Esto se debe a que cuando Bash ve esta línea, no ve nada especial y, por lo tanto, expande todas las variables y finalmente ve:

[[ -d . ]]

En este momento, las expansiones de alias y el escaneo de palabras clave se han realizado durante mucho tiempo y ya no se realizarán, por lo que Bash intenta encontrar el comando llamado [[ , no lo encuentra y se queja.

En la misma linea:

$ '[' -d . ]
$ echo $?
0
$ '[[' -d . ]]
bash: [[: command not found

y

$ \[ -d . ]
$ echo $?
0
$ \[[ -d . ]]
bash: [[: command not found

La expansión de alias es algo bastante especial también. Todos ustedes han hecho lo siguiente al menos una vez:

$ alias ll='ls -l'
$ ll
.... <list of files in long format> ....
$ \ll
bash: ll: command not found
$ 'll'
bash: ll: command not found

El razonamiento es el mismo: la expansión de alias ocurre mucho antes de la expansión variable y la eliminación de comillas.


Keyword vs Alias

¿Qué crees que sucede si definimos un alias como palabra clave?

$ alias mytest='[['
$ mytest -d . ]]
$ echo $?
0

¡Oh, funciona! ¡para que los alias se puedan usar para alias de palabras clave! bueno saber.


Conclusión: los builtins realmente se comportan como comandos: corresponden a una acción que se ejecuta con argumentos que se someten a expansión directa de variables y división de palabras y globbing. Realmente es como tener un comando externo en algún lugar /bino /usr/binque se llama con los argumentos dados después de la expansión de variables, etc. Tenga en cuenta que cuando digo es realmente como tener un comando externo , solo me refiero a los argumentos, división de palabras, globbing, expansión variable, etc. ¡Un incorporado puede modificar el estado interno del shell!

Las palabras clave, por otro lado, se escanean y se entienden muy temprano, y permiten un comportamiento sofisticado del shell: el shell podrá prohibir la división de palabras o la expansión del nombre de ruta, etc.

Ahora mire la lista de palabras clave y palabras clave e intente averiguar por qué algunas deben ser palabras clave.


!es una palabra clave Parece que sería posible imitar su comportamiento con una función:

not() {
    if "$@"; then
        return false
    else
        return true
    fi
}

pero esto prohibiría construcciones como:

$ ! ! true
$ echo $?
0

o

$ ! { true; }
echo $?
1

Lo mismo para time: es más poderoso tener una palabra clave para que pueda cronometrar comandos compuestos complejos y tuberías con redireccionamientos:

$ time grep '^#' ~/.bashrc | { i=0; while read -r; do printf '%4d %s\n' "$((++i))" "$REPLY"; done; } > bashrc_numbered 2>/dev/null

Si fuera timeun mero comando (incluso incorporado), solo vería los argumentos grep, ^#y /home/gniourf/.bashrc, cronometraría esto, y luego su salida pasaría por las partes restantes de la tubería. Pero con una palabra clave, ¡Bash puede manejar todo! ¡puede timela tubería completa, incluidas las redirecciones! Si timefuera un mero comando, no podríamos hacer:

$ time { printf 'hello '; echo world; }

Intentalo:

$ \time { printf 'hello '; echo world; }
bash: syntax error near unexpected token `}'

Intenta arreglarlo:

$ \time { printf 'hello '; echo world;
time: cannot run {: No such file or directory

Sin esperanza.


Palabra clave vs alias?

$ alias mytime=time
$ alias myls=ls
$ mytime myls

¿Qué crees que pasa?


Realmente, un builtin es como un comando, excepto que está construido en el shell, ¡mientras que una palabra clave es algo que permite un comportamiento sofisticado! podemos decir que es parte de la gramática del shell.


2
De acuerdo con @JohnyTex, esta es una de las respuestas más completas y apropiadas que he visto en los sitios de Stack. Gracias. Una pregunta tal vez no relacionada: simplemente por curiosidad Estoy tratando de encontrar la documentación para el 'desactivar temporalmente alias' funcionalidad del que precede a un comando con =\'mediante el uso man, aproposy help, y no he tenido suerte. ¿Alguna idea de dónde iría para encontrar esta información? Principalmente para que en el futuro pueda ver qué más hay allí, porque creo que me falta una fuente de referencia.
nc.

@nc: no lo encontrará documentado explícitamente. La razón por la que funciona se explica en esta respuesta. Lo más cercano que encontrará está en el manual de referencia en la sección Operación de Shell . Verá que la expansión de alias se realiza muy temprano (algo que traté de enfatizar en esta respuesta), en el paso 2. La eliminación de comillas, la expansión de parámetros, el bloqueo, etc. se realizan más adelante. Entonces, para deshabilitar un alias, puede usar algún tipo de cita para prohibir que el shell entienda un token como un alias, por ejemplo \ll, "ll"o 'll'.
gniourf_gniourf

1
De hecho, recibí el orden, los alias y las palabras clave que se activan ocurren primero. Como citamos el [[rendimiento \[[, no se analiza como un alias. Derecho hasta ahora? Donde me perdí no me di cuenta de que la barra invertida era una cosa de citas en Bash, e intenté buscarlo bajo alias y palabras clave y me perdí por completo ... Todo bien ahora. En la sección Cita:> Una barra invertida no citada () es el carácter de escape.
nc.

1
Por lo tanto, podemos pensar en (2) en esa lista de Op como Tokenization, y \[[es un token único del tipo LiteralQuoteToken, en oposición al [[cual será OpenTestKeywordToken, y eso requiere un ]]CloseTestKeywordToken de cierre para la compilación oroper / sintaxis correcta. Más tarde, en (4) LiteralQuoteToken se evaluará [[como el nombre del comando / comando a ejecutar, y en (6) Bash vomitará porque no hay ningún [[comando o comando incorporado. Agradable. Puedo olvidar los detalles precisos con el tiempo, pero en este momento la forma en que Bash ejecuta las cosas es mucho más clara para mí; gracias.
nc.

9

man bashlos llama SHELL BUILTIN COMMANDS. Entonces, un "shell incorporado" es como un comando normal, como grep, etc., pero en lugar de estar contenido en un archivo separado, está integrado en el propio bash . Esto los hace funcionar más eficientemente que los comandos externos.

Una palabra clave también está "codificada en Bash, pero a diferencia de una función incorporada, una palabra clave no es en sí misma un comando, sino una subunidad de una construcción de comando". Interpreto que esto significa que las palabras clave no tienen una función sola, sino que requieren comandos para hacer cualquier cosa. (Desde el enlace, otros ejemplos son for, while, do, y !, y hay más en mi respuesta a su otra pregunta.)


1
Dato interesante: [[ is a shell keywordpero [ is a shell builtin. No tengo ni idea de porqué.
Sparhawk

1
Es probable que por razones históricas y el estándar POSIX se ajusten lo más posible al antiguo shell Bourne, ya que [existía como un comando separado en el día. [[no está especificado por el estándar, por lo que los desarrolladores pueden elegir incluir eso como palabra clave o como incorporado.
Sergiy Kolodyazhnyy

1

El manual de la línea de comandos que viene con Ubuntu no proporciona una definición de palabras clave, sin embargo, el manual en línea (ver nota al margen) y las especificaciones estándar del lenguaje de comando POSIX Shell , se refieren a estas como "palabras reservadas", y ambas proporcionan listas de ellas. Del estándar POSIX:

Este reconocimiento solo ocurrirá cuando no se cite ninguno de los caracteres y cuando la palabra se use como:

  • La primera palabra de un comando

  • La primera palabra que sigue a una de las palabras reservadas que no sea caso, para o en

  • La tercera palabra en un comando de caso (solo en es válido en este caso)

  • La tercera palabra en un comando for (solo en y do son válidos en este caso)

La clave aquí es que las palabras clave / palabras reservadas tienen un significado especial porque facilitan la sintaxis de la shell, sirven para señalar ciertos bloques de código como bucles, comandos compuestos, ramificaciones (si / caso), etc. Permiten formar declaraciones de comandos, pero por sí mismos - no hacer nada, y de hecho, si se introduce palabras clave como for, until, case- la cáscara se espera una declaración completa, de lo contrario - error de sintaxis:

$ for
bash: syntax error near unexpected token `newline'
$  

En el nivel del código fuente, las palabras reservadas para bash se definen en parese.y , mientras que los incorporados tienen un directorio completo dedicado a ellos.

Nota al margen

El índice GNU se muestra [como palabra reservada, sin embargo, es un comando incorporado de hecho. [[por el contrario es una palabra reservada.

Ver también: ¿ Diferencias entre palabra clave, palabra reservada y construcción?

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.