Escribí un script bash y lo ejecuté sin compilarlo primero. Funcionó perfectamente. Puede funcionar con o sin permisos, pero cuando se trata de programas en C, necesitamos compilar el código fuente. ¿Por qué?
Escribí un script bash y lo ejecuté sin compilarlo primero. Funcionó perfectamente. Puede funcionar con o sin permisos, pero cuando se trata de programas en C, necesitamos compilar el código fuente. ¿Por qué?
Respuestas:
Significa que los scripts de shell no se compilan, se interpretan: el shell interpreta los scripts de un comando a la vez y descubre cada vez cómo ejecutar cada comando. Eso tiene sentido para los scripts de shell, ya que de todos modos pasan la mayor parte de su tiempo ejecutando otros programas.
Los programas C, por otro lado, generalmente se compilan: antes de que se puedan ejecutar, un compilador los convierte a código de máquina en su totalidad, de una vez por todas. Ha habido intérpretes de C en el pasado (como el intérprete de C de HiSoft en el Atari ST) pero fueron muy inusuales. Hoy en día los compiladores de C son muy rápidos; TCC es tan rápido que puede usarlo para crear "scripts C", con un #!/usr/bin/tcc -run
shebang, para que pueda crear programas C que se ejecuten de la misma manera que los scripts de shell (desde la perspectiva de los usuarios).
Algunos idiomas suelen tener un intérprete y un compilador: BASIC es un ejemplo que me viene a la mente.
También puede encontrar los llamados compiladores de script de shell, pero los que he visto son solo envoltorios ofuscantes: todavía usan un shell para interpretar realmente el script. Como señala mtraceur , sin duda sería posible un compilador de script de shell adecuado, pero no muy interesante.
Otra forma de pensar en esto es considerar que la capacidad de interpretación de scripts de un shell es una extensión de su capacidad de manejo de línea de comandos, lo que naturalmente conduce a un enfoque interpretado. C, por otro lado, fue diseñado para producir binarios independientes; Esto lleva a un enfoque compilado. Los lenguajes que generalmente se compilan tienden a generar también intérpretes, o al menos analizadores de línea de comandos (conocidos como REPL, bucles read-eval-print ; un shell es en sí mismo un REPL).
execve
, open
, close
, read
, write
, y pipe
llamadas al sistema, intercalados con algunos getenv
, setenv
y las operaciones de HashMap interna / matriz (por variables no exportados ), etc. Bourne Shell y sus derivados tampoco son lenguajes de programación que se benefician tanto de los ajustes del compilador de bajo nivel como el reordenamiento de código, etc.
Considere el siguiente programa:
2 Mars Bars
2 Milks
1 Bread
1 Corn Flakes
En el bash
camino, deambulas por la tienda buscando barras de Marte, finalmente las localizas, luego deambulas buscando leche, etc. Esto funciona porque estás ejecutando un programa complejo llamado "Comprador experimentado" que puede reconocer un pan cuando ves uno. y todas las otras complejidades de las compras. bash
Es un programa bastante complejo.
Alternativamente, puede entregar su lista de compras a un compilador de compras. El compilador piensa por un momento y te da una nueva lista. Esta lista es LARGA , pero consta de instrucciones mucho más simples:
... lots of instructions on how to get to the store, get a shopping cart etc.
move west one aisle.
move north two sections.
move hand to shelf three.
grab object.
move hand to shopping cart.
release object.
... and so on and so forth.
Como puede ver, el compilador sabe exactamente dónde está todo en la tienda, por lo que no es necesaria toda la fase de "buscar cosas".
Este es un programa en sí mismo y no necesita "Comprador experimentado" para ejecutarse. Todo lo que necesita es un humano con "Sistema operativo humano básico".
Volviendo a los programas de computadora: bash
es "Comprador experimentado" y puede tomar un script y simplemente hacerlo sin compilar nada. El compilador de CA produce un programa independiente que ya no necesita ayuda para ejecutarse.
Tanto los intérpretes como los compiladores tienen sus ventajas y desventajas.
Todo se reduce a la diferencia técnica entre cómo el programa que puede leer / escribir como humano se convierte en las instrucciones de la máquina que su computadora comprende, y las diferentes ventajas y desventajas de cada método es la razón por la cual algunos idiomas están escritos para necesitar compiladores , y algunos están escritos para ser interpretados.
(Nota: estoy simplificando mucho aquí para abordar la pregunta. Para una comprensión más profunda, las notas técnicas al final de mi respuesta elaboran / refinan algunas de las simplificaciones aquí, y los comentarios sobre esta respuesta tienen algunas aclaraciones útiles y discusión también ..)
Básicamente, hay dos categorías generales de lenguajes de programación:
C está en la primera categoría (el compilador de C traduce el lenguaje C al código de máquina de su computadora : el código de máquina se guarda en un archivo, y luego cuando ejecuta ese código de máquina, hace lo que desea).
bash está en la segunda categoría (el intérprete bash lee el lenguaje bash y el intérprete bash hace lo que usted desea: por lo que no hay un "módulo compilador" per se, el intérprete interpreta y ejecuta, mientras que un compilador lee y traduce) .
Es posible que ya haya notado lo que esto significa:
Con C, realiza el paso de "interpretación" una vez , luego, cada vez que necesita ejecutar el programa, simplemente le dice a su computadora que ejecute el código de la máquina; su computadora puede ejecutarlo directamente sin tener que hacer ningún "pensamiento" adicional.
Con bash, debe hacer el paso de "interpretar" cada vez que ejecuta el programa: su computadora ejecuta el intérprete de bash, y el intérprete de bash hace un "pensamiento" adicional para descubrir qué necesita hacer para cada comando, cada vez .
Por lo tanto, los programas C requieren más CPU, memoria y tiempo para prepararse (el paso de compilación) pero menos tiempo y trabajo para ejecutarse. Los programas bash requieren menos CPU, memoria y tiempo para prepararse, pero más tiempo y trabajo para ejecutarse. Probablemente no note estas diferencias la mayoría de las veces porque las computadoras son muy rápidas hoy en día, pero sí hace una diferencia, y esa diferencia se suma cuando necesita ejecutar programas grandes o complicados, o muchos programas pequeños.
Además, debido a que los programas C se convierten en código de máquina (el "idioma nativo") de la computadora, no puede tomar un programa y copiarlo en otra computadora con un código de máquina diferente (por ejemplo, Intel de 64 bits en Intel 32 -bit, o de Intel a ARM o MIPS o lo que sea). Tienes que pasar el tiempo para compilarlo para ese otro lenguaje de máquina nuevamente . Pero un programa bash se puede mover a otra computadora que tenga instalado el intérprete bash, y funcionará bien.
Los creadores de C estaban escribiendo un sistema operativo y otros programas en hardware desde hace varias décadas, que estaba bastante limitado por los estándares modernos. Por varias razones, convertir los programas en el código de máquina de la computadora era la mejor manera de lograr ese objetivo para ellos en ese momento. Además, estaban haciendo el tipo de trabajo en el que era importante que el código que escribieran funcionara de manera eficiente .
Y los creadores del shell Bourne y bash querían lo contrario: querían escribir programas / comandos que pudieran ejecutarse de inmediato: en la línea de comandos, en una terminal, solo desea escribir una línea, un comando y tenerlo ejecutar. Y querían scripts que usted escribiera para trabajar en cualquier lugar donde tuviera instalado el intérprete / programa shell.
En resumen, no necesita un compilador para bash, pero necesita uno para C porque esos lenguajes se convierten en acciones informáticas reales de manera diferente, y esa forma diferente de hacerlo se eligió porque los lenguajes tenían objetivos diferentes.
En realidad, podría crear un intérprete de C o un compilador de bash. No hay nada que impida que eso sea posible: solo esos lenguajes fueron creados para diferentes propósitos. A menudo es más fácil reescribir el programa en otro idioma que escribir un buen intérprete o compilador para un lenguaje de programación complejo. Especialmente cuando esos idiomas tienen algo específico en lo que eran buenos, y fueron diseñados con una cierta forma de trabajar en primer lugar. C fue diseñado para ser compilado, por lo que le faltan muchos métodos abreviados convenientes que desearía en un shell interactivo, pero es muy bueno para expresar una manipulación de datos / memoria muy específica y de bajo nivel e interactuar con el sistema operativo , que son tareas que a menudo te encuentras haciendo cuando quieres escribir código compilado eficientemente. Mientras tanto, bash es muy bueno para ejecutar otros programas,
Detalles más avanzados: en realidad, hay lenguajes de programación que son una combinación de ambos tipos (traducen el código fuente "la mayor parte del tiempo", para que puedan hacer la mayor parte de la interpretación / "pensar" una vez, y hacer solo un poquito de la interpretación / "pensamiento" más adelante). Java, Python y muchos otros lenguajes modernos son en realidad tales mezclas: intentan obtener algunos de los beneficios de portabilidad y / o desarrollo rápido de los lenguajes interpretados, y algo de la velocidad de los lenguajes compilados. Hay muchas formas posibles de combinar dichos enfoques, y los diferentes idiomas lo hacen de manera diferente. Si desea profundizar en este tema, puede leer sobre lenguajes de programación compilando en "bytecode" (que es como compilar en su propio "lenguaje de máquina" inventado
Usted preguntó por el bit de ejecución: en realidad, el bit de ejecución sólo está allí para indicar al sistema operativo que está ese archivo permitido para ser ejecutado. Sospecho que la única razón por la que los scripts de bash funcionan para usted sin el permiso de ejecución es porque los está ejecutando desde dentro de un shell de bash. Normalmente, el sistema operativo, cuando se le pide que ejecute un archivo sin el bit de ejecución establecido, simplemente devolverá un error. Pero algunos shells como bash verán ese error y se encargarán de ejecutar el archivo de todos modos, básicamente emulando los pasos que normalmente tomaría el sistema operativo (busque la línea "#!" Al comienzo del archivo e intente ejecutar ese programa para interpretar el archivo, con un valor predeterminado de sí mismo o /bin/sh
si no hay una línea "#!").
A veces, un compilador ya está instalado en su sistema, y a veces los IDE vienen con su propio compilador y / o ejecutan la compilación por usted. Esto puede hacer que un lenguaje compilado "se sienta" como un lenguaje no compilado para usar, pero la diferencia técnica sigue ahí.
Un lenguaje "compilado" no necesariamente se compila en código máquina, y la compilación completa es un tema en sí mismo. Básicamente, el término se usa ampliamente: en realidad puede referirse a algunas cosas. En un sentido específico, un "compilador" es solo un traductor de un idioma (típicamente un lenguaje de "nivel superior" más fácil de usar para los humanos) a otro idioma (típicamente un lenguaje de "nivel inferior" que es más fácil de usar para las computadoras) a veces, pero en realidad no muy a menudo, este es el código de máquina). Además, a veces, cuando la gente dice "compilador", en realidad están hablando de varios programas que trabajan juntos (para un compilador C típico, en realidad son cuatro programas: el "preprocesador", el compilador en sí, el "ensamblador" y el " enlazador ").
Los lenguajes de programación / scripting pueden compilarse o interpretarse.
Los ejecutables compilados son siempre más rápidos y se pueden detectar muchos errores antes de la ejecución.
Los lenguajes interpretados son generalmente más simples de escribir y adaptar son menos estrictos que los lenguajes compilados, y no requieren compilación, lo que los hace más fáciles de distribuir.
Imagine que el inglés no es su idioma nativo (eso podría ser bastante fácil para usted si el inglés no es su idioma nativo).
Hay 3 formas de leer esto:
Las computadoras tienen una especie de "idioma nativo": una combinación de instrucciones que comprende el procesador e instrucciones que comprende el sistema operativo (por ejemplo, Windows, Linux, OSX, etc.). Este lenguaje no es legible por los humanos.
Los lenguajes de secuencias de comandos, como Bash, generalmente se dividen en las categorías 1 y 2. Toman una línea a la vez, traducen esa línea y la ejecutan, luego pasan a la línea siguiente. En Mac y Linux, se instalan bastantes intérpretes diferentes por defecto para diferentes idiomas, como Bash, Python y Perl. En Windows, debe instalarlos usted mismo.
Muchos lenguajes de secuencias de comandos realizan un pequeño preprocesamiento: intente acelerar la ejecución compilando fragmentos de código que se ejecutarán con frecuencia o que de lo contrario ralentizarían la aplicación. Algunos de los términos que puede escuchar incluyen la compilación Ahead-of-time (AOT) o Just-in-time (JIT).
Por último, los lenguajes compilados, como C, traducen todo el programa antes de que pueda ejecutarlos. Esto tiene la ventaja de que la traducción se puede realizar en una máquina diferente a la ejecución, por lo que cuando le entrega el programa al usuario, aunque todavía puede haber errores, ya se pueden limpiar varios tipos de errores. Al igual que si le dieras esto a tu traductor, y menciono cómo garboola mizene resplunks
, eso podría parecer un inglés válido para ti, pero el traductor puede decirte que estoy hablando tonterías. Cuando ejecuta un programa compilado, no necesita un intérprete, ya está en el idioma nativo de la computadora
Sin embargo, hay un inconveniente en los lenguajes compilados: mencioné que las computadoras tienen un idioma nativo, compuesto por características del hardware y del sistema operativo; bueno, si compila su programa en Windows, no esperará que el programa compilado se ejecute en una Mac Algunos idiomas evitan esto compilando un tipo de idioma intermedio, un poco como el inglés Pidgin, de esa manera, obtienes los beneficios de un idioma compilado, así como un pequeño aumento de velocidad, pero eso significa que necesitas agrupar un intérprete con su código (o use uno que ya esté instalado).
Por último, su IDE probablemente estaba compilando sus archivos por usted y podría informarle sobre errores antes de ejecutar el código. A veces, esta comprobación de errores puede ser más profunda que lo que hará el compilador. Un compilador a menudo solo verificará tanto como sea necesario para que pueda producir código nativo sensible. Un IDE a menudo ejecutará algunas comprobaciones adicionales y puede decirle, por ejemplo, si ha definido una variable dos veces, o si ha importado algo que no ha utilizado.
Mucha gente habla de interpretación vs compilación, pero creo que esto puede ser un poco engañoso si lo miras de cerca, ya que algunos lenguajes interpretados se compilan en un bytecode intermedio antes de la ejecución.
Al final, la verdadera razón por la cual los programas C deben compilarse en formato ejecutable es que la computadora necesita hacer mucho trabajo para transformar el código en un archivo fuente C en algo que pueda ejecutar, por lo que tiene sentido guardar el producto de todo lo que funciona en un archivo ejecutable, por lo que no necesita volver a hacerlo cada vez que quiera ejecutar su programa.
Por otro lado, el intérprete de Shell necesita hacer muy poco trabajo para convertir un script de shell en "operaciones de máquina". Básicamente solo necesita leer el script línea por línea, dividirlo en espacios en blanco, configurar algunas redirecciones de archivos y tuberías y luego hacer un fork + exec. Dado que la sobrecarga de analizar y procesar la entrada de texto de un script de shell es muy pequeña en comparación con el tiempo que lleva iniciar los procesos en el script de shell, sería excesivo compilar los scripts de shell en un formato de máquina intermedio en lugar de simplemente interpretar El código fuente directamente.