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:
| Pregunta | Quién la hace | Qué 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:
#!/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:
$ 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.
- La shell pide ejecutar
./hola.sh uno dos, y espera. shell - El kernel abre el archivo y encuentra que los dos primeros bytes son
#!. kernel - Lee el resto de la línea:
/usr/bin/env bash. Es el intérprete, con un argumento. kernel - Ejecuta en su lugar
/usr/bin/env bash ./hola.sh uno dos. kernel - 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ínea | Al 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:
$ 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
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 Bash | Usa exactamente /bin/bash. | El programa env busca bash en los directorios de PATH, como haría la shell. |
| Ventaja | Predecible: 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. |
| Desventaja | Falla 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 conviene | Scripts 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:
#!/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:
$ 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:
| Comando | Resultado | Cuándo usarlo |
|---|---|---|
chmod +x hola.sh | -rwxr-xr-x | Lo 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-x | Lo 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:
$ 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.
| Forma | Necesita | Quién ejecuta las líneas |
|---|---|---|
./hola.sh | Permiso x y r, y línea shebang. | Un proceso nuevo, con el intérprete de la línea shebang. |
hola | Lo 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.sh | Solo permiso r. | Un proceso nuevo de bash. La línea shebang se ignora. |
source hola.sho . hola.sh | Solo permiso r. | Tu shell actual, sin crear ningún proceso. |
La variable especial $$ contiene el número de proceso de la shell que la evalúa, y permite comprobarlo:
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.
./?
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:
$ 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.
| Uso | Ejemplo |
|---|---|
| Releer la configuración de la shell sin cerrar la terminal | source ~/.bashrc |
| Cargar variables de configuración desde un archivo aparte | source /etc/respaldo.conf, que contiene líneas como DESTINO=/respaldos. Lo vemos en el tema 30. |
| Cargar una biblioteca de funciones escrita en otro archivo | source "$(dirname "$0")/lib/registro.sh". Es el tema 22. |
Activar un entorno que cambia PATH y otras variables | source .venv/bin/activate, en proyectos de Python. |
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:
| Directorio | Para quién | Cómo se instala |
|---|---|---|
~/bin | Solo tu usuario. Es el lugar tradicional. | cp hola.sh ~/bin/hola |
~/.local/bin | Solo 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/bin | Todos 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:
$ 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:
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.
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:
- Mientras el script está en desarrollo, dentro de un proyecto, lleva extensión:
respaldo.sh. Se reconoce de un vistazo y el editor lo colorea. - Cuando se instala como comando, pierde la extensión:
respaldo. A quien lo usa no le importa en qué lenguaje está escrito, y si mañana se reescribe en Python, el nombre no cambia y nada de lo que lo llama se rompe. Ninguno de los comandos del sistema tiene extensión. - Los archivos pensados para
sourceconservan la extensión, porque no son comandos:registro.sh,config.sh.
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:
$ 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:
| Mensaje | Causa | Solución |
|---|---|---|
hola.sh: command not found | Escribiste el nombre sin ruta, y no está en ningún directorio de PATH. | Usa ./hola.sh, o instálalo en ~/bin. |
Permission denied | Falta 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 directory | El 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 directory | La misma causa, con la línea env. | La misma solución. |
bad interpreter: No such file or directory, sin ^M | La 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: "(" unexpected | El 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 script | Bash 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 error | El 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:
$ ./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:
- Con
exit:exit 0termina el script en ese punto indicando éxito;exit 1, o cualquier valor entre 1 y 255, indicando un fallo. - Sin
exit: el script devuelve el código del último comando que ejecutó.
$ ./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:
#!/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"
| Parte | Qué contiene | Tema |
|---|---|---|
| Línea shebang | El intérprete. Siempre la primera línea. | Este |
| Cabecera | Qué hace el script y cómo se usa, en comentarios. Es lo primero que lee quien abre el archivo. | 4 |
| Opciones | set -euo pipefail, para que el script se detenga ante los errores. | 23 |
| Configuración | Los valores que podrían cambiar: rutas, plazos, servidores. Juntos, arriba, donde se encuentran fácil. | 5 |
| Funciones | Los bloques reutilizables, como el mensaje de uso. Se definen antes de usarse. | 18 |
| Programa principal | Validar 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.
$ 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:
#!/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"
$ 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
- Para usarse como un comando, un script necesita una línea shebang, permiso de ejecución y estar en un directorio de
PATH. - La línea shebang empieza con
#!y la lee el kernel: ejecuta el intérprete indicado, pasándole la ruta del script y sus argumentos. Para Bash, esa línea es un comentario. #!/usr/bin/env bashbusca Bash enPATHy es más portable;#!/bin/bashes más predecible. Conenvno se pueden pasar opciones al intérprete: van dentro del script, conset.- Sin línea shebang, un script puede funcionar desde Bash y fallar desde otros programas. Todo script la lleva.
- Un script necesita permiso de ejecución y también de lectura.
bash scriptsolo necesita lectura. ./script,scriptdesdePATHybash scriptlo ejecutan en un proceso hijo, cuyos cambios desaparecen al terminar.source script, o. script, ejecuta las líneas en la shell actual. Sirve para cargar configuración y bibliotecas, no para ejecutar scripts que usanexit.- Los scripts propios se instalan en
~/bino~/.local/bin; los de todos los usuarios, en/usr/local/bin. - La extensión
.shno significa nada para el sistema. Se usa durante el desarrollo y se quita al instalar el script como comando. - Antes de elegir un nombre,
type nombredebe respondernot found.testes el ejemplo clásico de nombre ocupado. bad interpretercon^Mo'bash\r'indica finales de línea de Windows; se corrige consed -i 's/\r$//'.- Un script devuelve el código de su último comando, o el que indique
exit. Si falla, debe terminar con un código distinto de cero. - La estructura recomendada es: línea shebang, cabecera, opciones, configuración, funciones y programa principal.
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
- Crea
hola.shcomo en la sección 2. Intenta ejecutarlo antes y después dechmod +x, y también conbash hola.shsin el permiso. - Comprueba con
filey conhead -c 2 hola.sh | od -ccómo ve el sistema tu script. - Crea
pid.shcon la líneaecho "Soy el proceso $$"y ejecútalo de las cuatro maneras de la sección 7. ¿Qué números se repiten? - Reproduce el experimento de
ir-a-logs.shde la sección 8 con un directorio que exista en tu equipo. - Crea
~/binsi no existe, asegúrate de que esté en tuPATHe instalahola.shallí con el nombrehola. Ejecútalo desde/tmp. - Cambia la primera línea de
hola.shpor#!/usr/bin/env bash -ey observa el error. Déjala como estaba. - 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 confiley corrígelo consed. - Instala
nuevo-scripten~/biny úsalo para crear el esqueleto de la tarea que elegiste en los ejercicios del tema 1.