C para Operativos

Aprendiendo C para una cursada feliz de Sistemas Operativos

She Bangs, She Bangs...

| Comments

Si alguna vez editaron un script UNIX, seguramente habrán visto que arrancan de manera similar: #!/bin/sh. Por algún motivo, esa línea mágica se llama Shebang1.

mi_script
1
2
#!/bin/sh
echo "¡Hola, mundo!"

Llamamos mi_script a este archivo.

Esa línea mágica es la responsable de que podamos correr un script ejecutando ./mi_script en la consola. Como dijimos en el primer post, para ejecutar un programa en UNIX, el programa tiene que tener permisos de ejecución2, y luego tenemos que tipear su ruta completa en la consola:

1
2
3
$ chmod +x mi_script
$ ./mi_script
¡Hola, mundo!

Al hacerlo, el sistema operativo carga el programa en memoria y lo ejecuta.

El problema es que un script es texto. Los programas compilados tienen las instrucciones que nuestro procesador entiende, entonces es relativamente trivial ejecutarlo3. Un script tiene texto. Y, adivinaste: el procesador no ejecuta texto. Entonces, ¿qué es lo que ocurre? ¿Por qué y cómo funciona la ejecución de scripts?

La clave es el shebang. Cuando el sistema operativo detecta que lo que pretendemos ejecutar es un script (digamos, no es un programa en algún formato binario que el sistema operativo comprenda), busca en la primer línea para encontrar este shebang. Esa ruta que se encuentra después del #! (en nuestro caso anterior, /bin/sh) es la que va a ser ejecutada, pasándole como primer parámetro la ruta de nuestro script. Sí: en /bin/sh tenés que tener un programa ejecutable para que la ejecución del script funcione. Si, además, querés que se ejecute como corresponde, ese /bin/sh debería entender que cuando se lo ejecuta con un parámetro, ese parámetro representa una ruta a un archivo que contiene código en un lenguaje que ese programa sepa interpretar.

En general, esto ocurre: por convención, en los sistemas UNIX /bin/sh es una Bourne Shell, o alguna otra shell con el modo de compatibilidad activado. ¿Qué es una Bourne Shell? Un programa capaz de interpretar scripts escritos en ese lenguaje. Dado que los programas que se ponen en los shebangs tienen como objetivo interpretar scripts, se los llamó (muy originalmente) intérpretes.

¿Existen otros intérpretes? Claro que sí. Python es un lenguaje interpretado, y uno puede hacer scripts en Python y ejecutarlos en la consola, como este. Lo mismo pasa con Ruby, perl, y tantos otros lenguajes.

Ahora, como somos curiosos, podríamos ponernos a jugar con esto y abusar un poquito. ¿Qué pasa si usamos otro programa como intérprete? ¿Podemos usar cualquier programa? Podemos, sí, pero los resultados van a cambiar.

Editemos nuestro script. Modifiquemos únicamente el shebang, poniéndole como intérprete /bin/cat. cat es un programa que imprime en pantalla el contenido de la ruta que le pasamos por parámetro. ¿Qué va a pasar cuando volvamos a ejecutar nuestro script?

1
2
3
$ ./mi_script
#!/bin/cat
echo "¡Hola, mundo!"

Claro que sí. Al ejecutar nuestro script, el sistema operativo ejecutó /bin/cat pasándole la ruta a mi_script como parámetro. cat, como siempre, abrió la ruta que le pasaron por parámetro y mostró su contenido.

Sigamos jugando. Usemos otro programa: echo. Cambiemos el intérprete por /bin/echo, y ejecutemos:

1
2
$ ./mi_script
./mi_script

Así es. echo imprime lo que sea que le pasemos como parámetro. Si lo que le pasamos es la ruta de nuestro script, como pasa con todas las ejecuciones de los scripts, echo va a imprimir eso mismo en la consola.

Otro intérprete “loco” que podemos poner es rm: #!/bin/rm. Ejecutemos:

1
2
3
$ ./mi_script
$ ls mi_script
ls: mi_script: No such file or directory

Así es, lo borramos.

Entonces, hagamos una última prueba para jugar. Escribamos un programa que liste todos los parámetros con los que fue ejecutado:

interprete.c
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main(int argc, char **argv) {
  int index;
  for(index = 0; index < argc; index++) {
    printf("  Parametro %d: %s\n", index, argv[index]);
  }
  return 0;
}

Compilemos y ejecutemos:

1
2
3
4
5
6
7
8
$ gcc interprete.c -o interprete
$ ./interprete
  Parametro 0: ./interprete
$ ./interprete primerParametro segundo 3ro
  Parametro 0: ./interprete
  Parametro 1: primerParametro
  Parametro 2: segundo
  Parametro 3: 3ro

El primer parámetro de cualquier programa C es el propio programa. Ahora bien, pongámoslo como intérprete de nuestro script:

mi_script
1
2
#!./interprete
echo "Este codigo nunca se ejecutara"

Y ejecutemos:

1
2
3
4
5
6
7
$ ./mi_script
  Parametro 0: ./interprete
  Parametro 1: ./mi_script
$ ./mi_script primerParametro
  Parametro 0: ./interprete
  Parametro 1: ./mi_script
  Parametro 2: primerParametro

Así es: se ejecuta el programa que pusimos en la shebang, y luego se le pasa todo lo que tipeemos en la consola. Y no sólo podemos poner un programa en el shebang, sino también parámetros:

mi_script
1
2
#!./interprete unParametroFijo
echo "Este codigo nunca se ejecutara"

Y ejecutemos:

1
2
3
4
5
6
7
8
9
$ ./mi_script
  Parametro 0: ./interprete
  Parametro 1: unParametroFijo
  Parametro 2: ./mi_script
$ ./mi_script otroParametro
  Parametro 0: ./interprete
  Parametro 1: unParametroFijo
  Parametro 2: ./mi_script
  Parametro 3: otroParametro

Creo que no queda mucho más para agregar. Espero que mis respuestas os haya iluminados :)


EDIT: Recordé que había algo más para agregar.

Muchas veces ví que podía escribir un script sin ponerle un shebang, y que de todos modos funcionaba. ¿Por qué? Bueno, porque de algún modo se defaultea. En Stackoverflow dicen que se ejecuta con la misma shell que estás usando en ese momento, pero esto no es comportamiento estándar: la documentación de la syscall execve dice que un script tiene que comenzar con un shebang válido4. Conviene no dar nada por sentado y especificar el intérprete que queremos usar, asegurándonos así la compatibilidad y buen funcionamiento.


  1. El caracter # en inglés se llama “hash” (y supongo que se lo llamará, también, “sha”), y el ! es el “bang”.

  2. ¡Gracias, Victor, por la corrección!

  3. trivial es un decir. Ejecutar un programa no tiene nada de trivial, pero digamos que la idea de cómo ejecutarlo es relativamente sencilla, al menos respecto a interpretar un script.

  4. Y, abajo de todo, en la documentación, muestra el código de un programa llamado myecho MUUUUUUUY similar a nuestro interprete.c :)

Comments