← Automatización con Scripts en Bash Bloque 1 · Del comando al script · Tema 2 de 42

Tema 2

Anatomía de un script

Un script es un archivo de texto, y sin embargo se ejecuta como si fuera un programa. Entre una cosa y la otra hay tres piezas: una primera línea que dice quién debe leerlo, un permiso que autoriza a ejecutarlo y un directorio donde la shell sabe buscarlo. Este tema las examina una por una, explica en qué proceso corre un script según cómo se lo invoque y termina con una estructura que servirá de molde para todos los scripts del curso.

1. De archivo de texto a comando

Al terminar este tema, un script tuyo debería poder usarse exactamente igual que ls o grep: escribiendo su nombre, desde cualquier directorio, con los argumentos que haga falta. Para llegar ahí hay que resolver tres preguntas, que corresponden a tres piezas del script:

PreguntaQuién la haceQué la responde
¿Dónde está?La shell, al ver un nombre sin ruta.Los directorios de la variable PATH.
¿Se puede ejecutar?El kernel, al recibir el pedido.El permiso de ejecución del archivo.
¿Qué programa lo entiende?El kernel, al abrirlo.La primera línea del archivo, el shebang.

El tema 41 del curso de Terminal, De la línea al archivo: tu primer script, ya presentó estas piezas de forma práctica, y el tema 34 explicó la variable PATH. Aquí las retomamos con más detalle, porque cuando algo falla al ejecutar un script, la causa está casi siempre en una de las tres.

2. El primer script, paso a paso

Empecemos por un script mínimo, que saluda e informa dónde se está ejecutando. Puedes crearlo con cualquier editor de texto; con nano sería nano hola.sh. Su contenido:

hola.sh
#!/usr/bin/env bash
# hola.sh — mi primer script: saluda y dice dónde se ejecuta
echo "Hola, $USER. Hoy es $(date +%A)."
echo "Estás en $(pwd), en el equipo $(hostname)."

Y el recorrido completo, desde el archivo recién guardado hasta verlo funcionar:

terminal
$ ls -l hola.sh
-rw-r--r-- 1 ana ana 152 oct 14 10:02 hola.sh
$ ./hola.sh                             # todavía no tiene permiso de ejecución
bash: ./hola.sh: Permission denied
$ chmod +x hola.sh
$ ls -l hola.sh
-rwxr-xr-x 1 ana ana 152 oct 14 10:02 hola.sh
$ ./hola.sh
Hola, ana. Hoy es miércoles.
Estás en /home/ana/scripts, en el equipo equipo.

Cuatro detalles de este ejemplo son el contenido del resto del tema: la primera línea, que empieza con #!; el error Permission denied y el chmod que lo resuelve; el ./ delante del nombre, sin el cual la shell no encontraría el archivo; y la extensión .sh, que, como veremos, es opcional.

3. La línea shebang

La línea shebang es la primera línea de un script cuando empieza con los caracteres #!. Indica la ruta del programa que debe interpretar el archivo. El nombre viene de hash (#) y bang (!).

Quien lee esa línea no es Bash, sino el kernel. Cuando la shell pide ejecutar ./hola.sh, el kernel abre el archivo y mira sus primeros bytes para saber qué clase de programa es. Un programa compilado empieza con una marca binaria que lo identifica; un script empieza con #!. En ese caso el kernel no ejecuta el archivo: ejecuta el programa indicado en la línea y le pasa la ruta del script como argumento.

  1. La shell pide ejecutar ./hola.sh uno dos, y espera. shell
  2. El kernel abre el archivo y encuentra que los dos primeros bytes son #!. kernel
  3. Lee el resto de la línea: /usr/bin/env bash. Es el intérprete, con un argumento. kernel
  4. Ejecuta en su lugar /usr/bin/env bash ./hola.sh uno dos. kernel
  5. Bash lee el script desde el principio. La primera línea empieza con #, así que para Bash es un comentario, y la ignora. bash

Es decir, escribir ./hola.sh uno dos equivale a escribir a mano el intérprete seguido del script. El mismo mecanismo sirve para cualquier lenguaje que use # como comentario:

Primera líneaAl ejecutar ./prog arg, el kernel ejecuta
#!/bin/bash/bin/bash ./prog arg
#!/usr/bin/env bash/usr/bin/env bash ./prog arg
#!/bin/sh/bin/sh ./prog arg
#!/usr/bin/env python3/usr/bin/env python3 ./prog arg
#!/usr/bin/awk -f/usr/bin/awk -f ./prog arg

El comando file confirma cómo ve el sistema el archivo:

terminal
$ file hola.sh
hola.sh: Bourne-Again shell script, ASCII text executable
$ head -c 2 hola.sh | od -c               # los dos primeros bytes
0000000   #   !
0000002
Tres reglas de la línea shebang

Tiene que ser la primera línea, y #! los dos primeros caracteres del archivo: ni una línea en blanco ni un espacio antes. La ruta del intérprete tiene que ser absoluta, porque el kernel no consulta PATH. Y la línea termina en un salto de línea de Unix: un retorno de carro de Windows al final pasa a formar parte del nombre del intérprete, como veremos en la sección 11.

4. #!/bin/bash o #!/usr/bin/env bash

Las dos formas son correctas, y verás ambas en scripts reales. Difieren en cómo se encuentra Bash:

#!/bin/bash#!/usr/bin/env bash
Cómo encuentra BashUsa exactamente /bin/bash.El programa env busca bash en los directorios de PATH, como haría la shell.
VentajaPredecible: siempre el mismo Bash, el del sistema, aunque el usuario tenga otro en su PATH.Portable: funciona aunque Bash esté en otro lugar, como en macOS con Homebrew, en FreeBSD (/usr/local/bin/bash) o en NixOS.
DesventajaFalla en sistemas donde Bash no está en /bin.Depende del PATH de quien lo ejecuta. Y solo admite un argumento: no se pueden agregar opciones al intérprete.
Cuándo convieneScripts de administración que corren como root en servidores Linux conocidos.Scripts que se comparten o que deben funcionar en sistemas distintos. Es la forma que usa este curso.

La limitación del argumento único merece un ejemplo, porque es una trampa frecuente. En Linux, el kernel pasa todo lo que sigue a la ruta del intérprete como un solo argumento. Con #!/bin/bash -e no hay problema; pero con env, la palabra bash -e llega entera, espacio incluido, como si fuera el nombre de un programa:

terminal · con la primera línea #!/usr/bin/env bash -e
$ ./estricto.sh
/usr/bin/env: 'bash -e': No such file or directory
/usr/bin/env: use -[v]S to pass options in shebang lines

La solución recomendada no es complicar la primera línea, sino activar las opciones dentro del script, en la segunda: set -euo pipefail, que estudiaremos en el tema 23. Así, además, las opciones se aplican también cuando alguien ejecuta el script con bash estricto.sh, que ignora la línea shebang.

5. Qué pasa si falta

Un script sin línea shebang a veces funciona, y eso es lo que lo vuelve traicionero. Si lo ejecutas desde Bash con ./script, el kernel lo rechaza porque no reconoce el formato, pero Bash, en lugar de rendirse, lo vuelve a intentar ejecutándolo él mismo. Desde otro programa, en cambio, no hay quien haga ese segundo intento:

terminal · un script sin primera línea
$ cat sin-shebang.sh
echo "Funciono... por casualidad"
$ ./sin-shebang.sh                      # desde Bash: Bash lo ejecuta por su cuenta
Funciono... por casualidad
$ python3 -c 'import subprocess; subprocess.run(["./sin-shebang.sh"])'
...
OSError: [Errno 8] Exec format error: './sin-shebang.sh'

Además, sin la línea shebang el intérprete depende de quién lo ejecute: desde Bash lo leerá Bash, pero desde zsh, desde cron o desde otro programa puede leerlo sh, y un script con características de Bash fallará. La regla es sencilla: todo script lleva línea shebang.

6. El permiso de ejecución

Como viste en el curso de Terminal, cada archivo tiene permisos de lectura (r), escritura (w) y ejecución (x) para su dueño, su grupo y los demás. Un archivo recién creado con un editor no tiene permiso de ejecución, y el kernel se niega a ejecutarlo. Se agrega con chmod:

ComandoResultadoCuándo usarlo
chmod +x hola.sh-rwxr-xr-xLo habitual: todos pueden ejecutarlo, solo tú modificarlo. Respeta la máscara umask.
chmod u+x hola.sh-rwxr--r--Solo tú puedes ejecutarlo.
chmod 700 hola.sh-rwx------Solo tú puedes leerlo y ejecutarlo. Útil si contiene datos que otros no deben ver.
chmod 755 hola.sh-rwxr-xr-xLo mismo que +x, en notación octal. Es lo usual para scripts instalados para todos.

Hay una diferencia entre los scripts y los programas compilados que conviene conocer: un script necesita también permiso de lectura. Un programa compilado lo carga el kernel; un script lo tiene que leer el intérprete, que corre con los permisos de quien lo ejecuta. Con x pero sin r, el kernel pone en marcha Bash, y Bash no puede abrir el archivo:

terminal
$ chmod 311 hola.sh                      # -wx--x--x: ejecución sin lectura
$ ./hola.sh
bash: ./hola.sh: Permission denied
$ chmod 755 hola.sh                      # lo dejamos como estaba

A la inversa, el permiso de ejecución no es imprescindible si se nombra al intérprete de forma explícita: bash hola.sh funciona aunque el archivo solo tenga r, porque lo que se ejecuta es bash, y el script es apenas un archivo que lee. Esto tiene una consecuencia práctica: algunos sistemas montan /tmp o las memorias USB con la opción noexec, que prohíbe ejecutar archivos allí. Un ./script fallará con Permission denied aunque tenga x, y bash script seguirá funcionando.

7. Cuatro maneras de ejecutarlo

Hay cuatro formas de hacer que Bash ejecute un script, y no son equivalentes. La diferencia más importante no está en qué escribes, sino en qué proceso ejecuta las líneas del script.

FormaNecesitaQuién ejecuta las líneas
./hola.shPermiso x y r, y línea shebang.Un proceso nuevo, con el intérprete de la línea shebang.
holaLo anterior, y que el archivo esté en un directorio de PATH.Un proceso nuevo, con el intérprete de la línea shebang.
bash hola.shSolo permiso r.Un proceso nuevo de bash. La línea shebang se ignora.
source hola.sh
o . hola.sh
Solo permiso r.Tu shell actual, sin crear ningún proceso.
Desde la shell interactiva, ejecutar con ./script.sh o bash script.sh crea un proceso bash hijo, cuyos cambios de directorio y variables desaparecen al terminar; source script.sh ejecuta las líneas en la misma shell, y sus cambios permanecen crea un proceso hijo lee las líneas aquí Tu shell interactiva bash · PID 2140 ./script.sh · bash script.sh un bash nuevo · PID 2391 sus cd y variables mueren con él source script.sh · . script.sh la misma shell · PID 2140 sus cd y variables permanecen
Un script ejecutado normalmente corre en un proceso hijo: lo que cambia en su entorno no afecta a la shell que lo lanzó. Con source, las líneas se ejecutan en la shell actual.

La variable especial $$ contiene el número de proceso de la shell que la evalúa, y permite comprobarlo:

terminal · con un script que contiene echo "Soy el proceso $$"
$ echo "Mi shell es el proceso $$"
Mi shell es el proceso 2140
$ ./pid.sh
Soy el proceso 2391
$ bash pid.sh
Soy el proceso 2398
$ source pid.sh
Soy el proceso 2140

Que el script corra en un proceso hijo es casi siempre lo que se quiere. El script no puede alterar por accidente la shell desde la que trabajas: si cambia de directorio, define variables o activa opciones, todo eso desaparece cuando termina. Recibe una copia de las variables exportadas de tu shell, pero lo que haga con ellas no vuelve hacia atrás.

¿Por qué hace falta el ./?

Cuando un nombre de comando no contiene ninguna barra, la shell lo busca solo en los directorios de PATH, y el directorio actual no está entre ellos. Si escribes hola.sh, la respuesta es command not found. Con ./hola.sh le das una ruta, y la shell ya no busca: usa esa. Que el directorio actual no esté en PATH es deliberado: si lo estuviera, un archivo llamado ls dejado en un directorio compartido se ejecutaría en lugar del verdadero.

8. source: ejecutar en la shell actual

El comando source, que también se escribe con un punto, ., lee un archivo y ejecuta sus líneas en la shell actual, como si las escribieras tú. La diferencia con ejecutarlo se ve con un script que cambia de directorio y define una variable:

terminal
$ cat ir-a-logs.sh
#!/usr/bin/env bash
cd ~/app/logs
destino="servidor-de-respaldos"
echo "Ahora estoy en $(pwd)"
$ pwd
/home/ana
$ ./ir-a-logs.sh
Ahora estoy en /home/ana/app/logs
$ pwd                                   # el cd ocurrió en otro proceso
/home/ana
$ echo "[$destino]"                     # y la variable también
[]
$ source ir-a-logs.sh
Ahora estoy en /home/ana/app/logs
$ pwd
/home/ana/app/logs
$ echo "[$destino]"
[servidor-de-respaldos]

Por eso source se usa para un tipo particular de archivo: los que existen precisamente para modificar la shell actual o el script que los carga.

UsoEjemplo
Releer la configuración de la shell sin cerrar la terminalsource ~/.bashrc
Cargar variables de configuración desde un archivo apartesource /etc/respaldo.conf, que contiene líneas como DESTINO=/respaldos. Lo vemos en el tema 30.
Cargar una biblioteca de funciones escrita en otro archivosource "$(dirname "$0")/lib/registro.sh". Es el tema 22.
Activar un entorno que cambia PATH y otras variablessource .venv/bin/activate, en proyectos de Python.
source y exit no se llevan bien

Si un archivo cargado con source ejecuta exit, no termina el script: termina tu shell, y la ventana de la terminal se cierra. Por la misma razón, un set -e dentro de ese archivo queda activo en tu sesión, y el primer comando que falle la cerrará. Un script pensado para ejecutarse no debe cargarse con source, y un archivo pensado para cargarse con source no debe usar exit.

9. Instalarlo: ~/bin y el PATH

Para que un script se use como cualquier comando, escribiendo solo su nombre, tiene que estar en uno de los directorios de PATH. Las opciones habituales son tres:

DirectorioPara quiénCómo se instala
~/binSolo tu usuario. Es el lugar tradicional.cp hola.sh ~/bin/hola
~/.local/binSolo tu usuario. Es el lugar que recomienda el estándar actual, y el que usan muchos instaladores.cp hola.sh ~/.local/bin/hola
/usr/local/binTodos los usuarios de la máquina.sudo install -m 755 hola.sh /usr/local/bin/hola

En Ubuntu y Debian, el archivo ~/.profile agrega ~/bin y ~/.local/bin a PATH automáticamente, pero solo si existen al iniciar la sesión. Si acabas de crear el directorio, hay que cerrar la sesión y volver a entrar, o releer ese archivo:

terminal
$ mkdir -p ~/bin
$ cp hola.sh ~/bin/hola
$ source ~/.profile                     # o cerrar sesión y volver a entrar
$ echo "$PATH" | tr ':' '\n' | head -3
/home/ana/bin
/home/ana/.local/bin
/usr/local/sbin
$ cd /tmp
$ hola
Hola, ana. Hoy es miércoles.
Estás en /tmp, en el equipo equipo.
$ type hola
hola is /home/ana/bin/hola

Si tu distribución no lo hace sola, agrega esta línea al final de ~/.bashrc y ábrela de nuevo con source ~/.bashrc:

~/.bashrc
export PATH="$HOME/bin:$PATH"

El orden importa: los directorios se recorren de izquierda a derecha, y gana el primero que tenga un archivo con ese nombre. Poner ~/bin al principio permite que un script tuyo reemplace a un comando del sistema; ponerlo al final, que nunca lo haga. Lo primero es cómodo y lo segundo más seguro. Lo habitual es ponerlo al principio y elegir bien los nombres.

Instalar sin copiar

Si el script vive en un proyecto que editas a menudo, en lugar de copiarlo puedes crear un enlace simbólico: ln -s ~/proyectos/herramientas/hola.sh ~/bin/hola. Así, cada cambio que hagas en el proyecto queda instalado al guardar, sin repetir la copia.

10. Nombres y extensiones

Para Linux, la extensión .sh no significa nada: lo que decide cómo se ejecuta un archivo es su línea shebang, no su nombre. La extensión es solo una ayuda para las personas y para los editores, que la usan para colorear la sintaxis. De ahí una convención muy extendida:

Para el nombre en sí conviene usar minúsculas, sin espacios ni tildes, con guiones para separar palabras: limpiar-registros, no Limpiar Registros.sh. Y antes de elegirlo, comprobar que no esté ocupado:

terminal
$ type respaldo
bash: type: respaldo: not found             # libre
$ type test
test is a shell builtin                       # ocupado, y por un comando interno
$ type -a test
test is a shell builtin
test is /usr/bin/test
test is /home/ana/bin/test

El caso de test es el error clásico: es un comando interno de Bash, y los comandos internos tienen prioridad sobre cualquier archivo de PATH. Un script llamado test nunca se ejecutará escribiendo su nombre; en su lugar correrá el test interno, que no muestra nada. type -a lista todas las coincidencias en el orden en que se buscan.

11. Errores frecuentes al ejecutar

Cuando un script no arranca, el mensaje de error dice casi siempre cuál de las piezas falla. Estos son los que vas a encontrar:

MensajeCausaSolución
hola.sh: command not foundEscribiste el nombre sin ruta, y no está en ningún directorio de PATH.Usa ./hola.sh, o instálalo en ~/bin.
Permission deniedFalta el permiso de ejecución, o el de lectura, o el sistema de archivos está montado con noexec.chmod +x; revisa con ls -l; o ejecútalo con bash script.
/bin/bash^M: bad interpreter: No such file or directoryEl archivo tiene finales de línea de Windows (CRLF). El ^M es el retorno de carro, que quedó pegado al nombre del intérprete.Convierte el archivo: sed -i 's/\r$//' script o dos2unix script.
/usr/bin/env: 'bash\r': No such file or directoryLa misma causa, con la línea env.La misma solución.
bad interpreter: No such file or directory, sin ^MLa ruta de la línea shebang no existe, como #!/bin/bassh o #!/usr/local/bin/bash en un sistema que no lo tiene.Corrige la ruta, o usa #!/usr/bin/env bash.
[[: not found, o Syntax error: "(" unexpectedEl script usa características de Bash pero lo está ejecutando sh: por la primera línea, o porque se lo invocó con sh script.Primera línea #!/usr/bin/env bash, y ejecutarlo con ./script o bash script.
No such file or directory, tras renombrar o mover el scriptBash recuerda dónde encontró cada comando, y busca en la ubicación anterior.hash -r, que borra esa memoria.
No pasa nada, y no hay errorEl nombre coincide con un comando interno o con otro que está antes en PATH.type -a nombre, y renombrar el script.

El problema de los finales de línea merece atención si trabajas con Windows y WSL, o si editas en Windows archivos que luego se ejecutan en un servidor Linux. Windows termina cada línea con dos caracteres, retorno de carro y salto de línea; Linux, solo con el salto. El retorno de carro sobrante es invisible en el editor, pero Bash lo ve:

terminal
$ ./desde-windows.sh
/usr/bin/env: 'bash\r': No such file or directory
$ file desde-windows.sh
desde-windows.sh: Bourne-Again shell script, ASCII text executable, with CRLF line terminators
$ sed -i 's/\r$//' desde-windows.sh       # quita el \r del final de cada línea
$ ./desde-windows.sh
Funciona.

Para no repetirlo, configura tu editor para guardar con finales de línea de Unix (en Visual Studio Code aparece como LF en la barra inferior), y si usas Git, agrega a tu repositorio un archivo .gitattributes con la línea *.sh text eol=lf.

12. Cómo termina un script

Todo programa, al terminar, devuelve un número, su código de salida: 0 si salió bien, otro valor si algo falló. Un script también, y lo puede fijar de dos maneras:

terminal
$ ./hola.sh > /dev/null; echo "código: $?"
código: 0
$ cat comprobar-disco.sh
#!/usr/bin/env bash
# Termina con 1 si el disco raíz supera el 90 % de uso.
uso=$(df --output=pcent / | tail -1 | tr -dc '0-9')
echo "Uso del disco: $uso %"
if (( uso > 90 )); then
  exit 1
fi
$ ./comprobar-disco.sh; echo "código: $?"
Uso del disco: 61 %
código: 0

El código de salida es la forma en que un script le informa el resultado a quien lo llamó, sea una persona, otro script o cron. Es tan importante que tiene un tema propio, el 10. Por ahora basta con una regla: si el script falla, que termine con un código distinto de cero.

13. La estructura de un script

Bash no impone ningún orden: un script puede ser una lista de comandos sin más. Pero a medida que crecen, los scripts que se leen bien comparten una misma estructura, y conviene adoptarla desde el principio. Este es el molde que usaremos en el curso, con un script de respaldo como ejemplo:

respaldo · estructura recomendada
#!/usr/bin/env bash
#
# respaldo — empaqueta un directorio en /respaldos, con la fecha en el nombre.
# Uso: respaldo DIRECTORIO
#
set -euo pipefail                          # 1. opciones de la shell

readonly DESTINO="/respaldos"              # 2. configuración

uso() {                                    # 3. funciones
  echo "Uso: ${0##*/} DIRECTORIO" >&2
  exit 2
}

# 4. programa principal
[[ $# -eq 1 ]] || uso
origen="$1"
archivo="$DESTINO/$(basename "$origen")-$(date +%F).tar.gz"
tar -czf "$archivo" -C "$(dirname "$origen")" "$(basename "$origen")"
echo "Respaldo creado: $archivo"
ParteQué contieneTema
Línea shebangEl intérprete. Siempre la primera línea.Este
CabeceraQué hace el script y cómo se usa, en comentarios. Es lo primero que lee quien abre el archivo.4
Opcionesset -euo pipefail, para que el script se detenga ante los errores.23
ConfiguraciónLos valores que podrían cambiar: rutas, plazos, servidores. Juntos, arriba, donde se encuentran fácil.5
FuncionesLos bloques reutilizables, como el mensaje de uso. Se definen antes de usarse.18
Programa principalValidar los argumentos y hacer el trabajo.Todo el curso

Un detalle de la función uso: $0 contiene la ruta con que se invocó el script, y ${0##*/} le quita todo hasta la última barra, de modo que el mensaje muestra respaldo aunque se lo haya llamado como /home/ana/bin/respaldo. Es una expansión de parámetros, el contenido del tema 6.

terminal
$ respaldo
Uso: respaldo DIRECTORIO
$ echo $?
2
$ respaldo ~/proyecto
Respaldo creado: /respaldos/proyecto-2026-10-14.tar.gz

14. Un script que crea scripts

Si todos los scripts empiezan igual, escribir ese comienzo a mano cada vez es justamente la clase de tarea repetitiva que este curso enseña a eliminar. Este pequeño script crea un archivo nuevo con la línea shebang, la cabecera y las opciones, y le da permiso de ejecución:

~/bin/nuevo-script
#!/usr/bin/env bash
#
# nuevo-script — crea un script de Bash vacío, listo para editar.
# Uso: nuevo-script NOMBRE
#
set -euo pipefail

nombre="${1:?Uso: nuevo-script NOMBRE}"

if [[ -e "$nombre" ]]; then
  echo "Error: $nombre ya existe; no lo sobrescribo." >&2
  exit 1
fi

cat > "$nombre" <<FIN
#!/usr/bin/env bash
#
# ${nombre##*/} — (describe aquí qué hace)
# Uso: ${nombre##*/}
#
set -euo pipefail

FIN

chmod +x "$nombre"
echo "Creado $nombre"
terminal
$ nuevo-script
/home/ana/bin/nuevo-script: line 8: 1: Uso: nuevo-script NOMBRE
$ nuevo-script limpiar-registros.sh
Creado limpiar-registros.sh
$ nuevo-script limpiar-registros.sh
Error: limpiar-registros.sh ya existe; no lo sobrescribo.
$ ls -l limpiar-registros.sh
-rwxr-xr-x 1 ana ana 125 oct 14 11:40 limpiar-registros.sh
$ cat limpiar-registros.sh
#!/usr/bin/env bash
#
# limpiar-registros.sh — (describe aquí qué hace)
# Uso: limpiar-registros.sh
#
set -euo pipefail

Contiene varias piezas que todavía no estudiamos: ${1:?mensaje}, que termina el script con ese mensaje si falta el argumento (tema 6); [[ -e ]], que comprueba si un archivo existe (tema 11); y <<FIN, un documento en línea, que manda a cat todo el texto hasta la palabra FIN (tema 31). Fíjate, sobre todo, en que el script comprueba antes de escribir: si el archivo ya existe, se niega, en lugar de borrar el trabajo de alguien. Es la cualidad que el tema 1 llamó prudencia.

15. Resumen

16. Preguntas de repaso

Intenta responder cada una antes de abrir la respuesta.

1. ¿Qué tres cosas necesita un script para poder ejecutarse escribiendo solo su nombre?

Una línea shebang que indique el intérprete, permiso de ejecución (y de lectura), y estar en un directorio incluido en la variable PATH.

2. ¿Quién lee la línea shebang, y qué hace con ella?

El kernel. Al ver que el archivo empieza con #!, ejecuta el programa indicado en esa línea y le pasa como argumentos la ruta del script y los argumentos originales.

3. Si prog empieza con #!/usr/bin/env bash, ¿qué ejecuta realmente el kernel cuando escribes ./prog a b?

/usr/bin/env bash ./prog a b. A su vez, env busca bash en PATH y lo ejecuta con ./prog a b.

4. ¿Por qué Bash no intenta ejecutar la línea shebang como un comando?

Porque empieza con #, y para Bash todo lo que sigue a un # al comienzo de una palabra es un comentario.

5. ¿Por qué falla #!/usr/bin/env bash -e en Linux, y cuál es la alternativa?

Porque el kernel pasa todo lo que sigue al intérprete como un único argumento, y env busca un programa llamado literalmente bash -e. La alternativa es dejar #!/usr/bin/env bash y escribir set -e (o set -euo pipefail) en la línea siguiente.

6. Un archivo tiene permisos -rw-r--r--. ¿Funciona ./script.sh? ¿Y bash script.sh?

./script.sh falla con Permission denied, porque no tiene permiso de ejecución. bash script.sh funciona, porque lo que se ejecuta es bash, que solo necesita leer el archivo.

7. ¿Por qué un script con permiso x pero sin r no puede ejecutarse?

Porque quien lo ejecuta en realidad es el intérprete, que corre con los permisos del usuario y necesita abrir y leer el archivo para saber qué hacer.

8. Un script hace cd /tmp. Lo ejecutas con ./script.sh. ¿En qué directorio quedas al terminar? ¿Y si lo ejecutas con source?

Con ./script.sh, en el mismo directorio en que estabas, porque el cd ocurrió en un proceso hijo que ya terminó. Con source, en /tmp, porque las líneas se ejecutaron en tu propia shell.

9. ¿Por qué no se debe cargar con source un script que usa exit?

Porque el exit se ejecuta en la shell actual y la termina: se cierra la sesión o la ventana de la terminal, en lugar de terminar solo el script.

10. ¿Por qué hay que escribir ./hola.sh y no basta con hola.sh?

Porque un nombre sin barras se busca solo en los directorios de PATH, y el directorio actual no está entre ellos, por seguridad. El ./ es una ruta, y con una ruta la shell no busca.

11. Creaste ~/bin y copiaste allí un script, pero la shell responde command not found. ¿Qué puede pasar?

Lo más probable es que ~/bin no esté en PATH porque no existía al iniciar la sesión: se resuelve con source ~/.profile o volviendo a entrar. También puede faltar el permiso de ejecución, o que no esté en PATH en esa distribución, en cuyo caso se agrega en ~/.bashrc.

12. ¿Por qué un script llamado test parece no hacer nada?

Porque test es un comando interno de Bash, y los internos tienen prioridad sobre los archivos de PATH. Se ejecuta el test interno, que sin argumentos no muestra nada.

13. Al ejecutar un script aparece /bin/bash^M: bad interpreter. ¿Qué significa y cómo se corrige?

El archivo tiene finales de línea de Windows, y el retorno de carro (^M) quedó pegado al nombre del intérprete. Se corrige quitándolos con sed -i 's/\r$//' script o dos2unix script, y configurando el editor para guardar con finales LF.

14. ¿Qué código de salida devuelve un script que no usa exit?

El del último comando que ejecutó.

15. ¿Por qué conviene quitar la extensión .sh al instalar un script como comando?

Porque a quien lo usa no le importa en qué lenguaje está escrito, y si algún día se reescribe en otro lenguaje, el nombre puede seguir siendo el mismo sin romper nada de lo que lo llama. Para el sistema la extensión no significa nada: el intérprete lo decide la línea shebang.

Para practicar

  1. Crea hola.sh como en la sección 2. Intenta ejecutarlo antes y después de chmod +x, y también con bash hola.sh sin el permiso.
  2. Comprueba con file y con head -c 2 hola.sh | od -c cómo ve el sistema tu script.
  3. Crea pid.sh con la línea echo "Soy el proceso $$" y ejecútalo de las cuatro maneras de la sección 7. ¿Qué números se repiten?
  4. Reproduce el experimento de ir-a-logs.sh de la sección 8 con un directorio que exista en tu equipo.
  5. Crea ~/bin si no existe, asegúrate de que esté en tu PATH e instala hola.sh allí con el nombre hola. Ejecútalo desde /tmp.
  6. Cambia la primera línea de hola.sh por #!/usr/bin/env bash -e y observa el error. Déjala como estaba.
  7. Simula un archivo de Windows con printf '#!/usr/bin/env bash\r\necho hola\r\n' > crlf.sh, dale permiso de ejecución e intenta ejecutarlo. Diagnostícalo con file y corrígelo con sed.
  8. Instala nuevo-script en ~/bin y úsalo para crear el esqueleto de la tarea que elegiste en los ejercicios del tema 1.