Programación · Lenguajes de programación
Tutorial de scripting en Bash para principiantes
Respuesta corta
Bash es la shell y lenguaje de scripting de línea de comandos de Unix que se usa en servidores Linux, terminales de macOS y pipelines de CI. Un script de Bash es un archivo de texto con comandos de shell, combinados con variables, condiciones, bucles, funciones y tuberías. Esta página muestra cómo escribir y ejecutar scripts, entrecomillar variables de forma segura, controlar el flujo, usar arreglos, redirigir la salida y manejar errores con set -euo pipefail.
Abrir Calculadora chmodQué es Bash y para qué se usa
Bash (Bourne Again SHell) es una shell de línea de comandos y lenguaje de scripting del Proyecto GNU. Lee los comandos que escribes, ejecuta programas y los conecta con tuberías y redirecciones. Un script de Bash es un archivo de texto con esos mismos comandos, así que todo lo que haces a mano en una terminal se puede automatizar.
Bash es la shell interactiva predeterminada en la mayoría de las distribuciones de Linux y está disponible en macOS (que ahora usa zsh por defecto, pero sigue incluyendo Bash) y en Windows mediante WSL o Git Bash. Desarrolladores, administradores de sistemas e ingenieros DevOps lo usan para scripts de compilación, despliegue, tareas de CI, mantenimiento de servidores y procesamiento rápido de datos con herramientas como grep, sed, awk y sort.
Escribe y ejecuta Hola, mundo
Un script empieza con una línea shebang que indica al sistema qué intérprete usar. Haz el archivo ejecutable con chmod y luego ejecútalo.
#!/usr/bin/env bash
# hello.sh
echo "Hello, world!"chmod +x hello.sh
./hello.sh
# Hello, world!
bash hello.sh # also works without chmod
bash --version # check which version you have#!/usr/bin/env bash busca Bash en el PATH, lo que es más portable que fijar /bin/bash. Si un script usa solo funciones de POSIX, puede usar #!/bin/sh, pero entonces no se permite la sintaxis exclusiva de Bash, como [[ ]] y los arreglos.
Variables, comillas y expansión
Asigna con name=value y lee con $name o ${name}. Toda variable es una cadena; Bash solo hace aritmética entera dentro de $(( )). Usa comillas dobles alrededor de las expansiones para que los espacios y los caracteres especiales se conserven.
name="Ada" # no spaces around =
count=3
echo "Hello, $name" # Hello, Ada
echo "Next: $((count + 1))" # Next: 4 (integer arithmetic)
today=$(date +%Y-%m-%d) # command substitution
echo "Today is $today"
file="my report.txt"
echo "${file%.txt}.pdf" # my report.pdf (strip suffix)
echo "${#name}" # 3 (string length)
echo "${missing:-default}" # default (fallback value)| Sintaxis | Significado |
|---|---|
"$var" | valor de var, conservado como una sola palabra |
'text' | texto literal, sin ninguna expansión |
$(command) | salida de un comando |
$((a + b)) | aritmética de enteros |
$1, $2, $@, $# | argumentos del script o de la función, todos los argumentos, número de argumentos |
$? | código de salida del último comando (0 significa éxito) |
Condiciones y case
if ejecuta un comando y comprueba su código de salida. El comando de prueba [[ ]] compara cadenas y comprueba archivos; (( )) compara números. case compara un valor con patrones glob.
#!/usr/bin/env bash
file="${1:-notes.txt}"
if [[ -f "$file" ]]; then
echo "$file exists"
elif [[ -d "$file" ]]; then
echo "$file is a directory"
else
echo "$file not found"
fi
n=7
if (( n % 2 == 1 )); then echo "$n is odd"; fi
case "$file" in
*.txt) echo "text file" ;;
*.sh) echo "shell script" ;;
*) echo "something else" ;;
esac| Prueba | Es verdadera cuando |
|---|---|
[[ -f path ]] | path es un archivo normal |
[[ -d path ]] | path es un directorio |
[[ -z "$s" ]] | la cadena está vacía |
[[ "$a" == "$b" ]] | las cadenas son iguales |
(( a > b )) | el número a es mayor que b |
[[ $a -lt $b ]] | el número a es menor que b (estilo antiguo) |
Bucles
Bash tiene for sobre una lista de palabras, un for (( )) al estilo de C, while y until. Para leer un archivo línea por línea se usa while read.
for fruit in apple banana cherry; do
echo "fruit: $fruit"
done
for (( i = 1; i <= 3; i++ )); do
echo "i = $i"
done
for f in *.log; do
[[ -e "$f" ]] || continue # skip if no .log files match
echo "log file: $f"
done
while IFS= read -r line; do
echo "line: $line"
done < input.txtIFS= read -r lee cada línea exactamente como está escrita: IFS= conserva los espacios iniciales y -r conserva las barras invertidas.
Funciones
Define una función con name() { ... } y llámala como un comando. Los argumentos llegan como $1, $2, etc. Una función devuelve un código de salida (de 0 a 255) con return; para devolver datos, imprímelos y captura la salida con $( ).
greet() {
local name="${1:-world}" # local variable, default value
echo "Hello, $name!"
}
is_even() {
(( $1 % 2 == 0 )) # exit status 0 means true
}
greet # Hello, world!
greet "Ann" # Hello, Ann!
msg=$(greet "Bo") # capture output
echo "Got: $msg" # Got: Hello, Bo!
if is_even 4; then echo "4 is even"; fiArreglos y arreglos asociativos
Los arreglos indexados contienen una lista de cadenas. Los arreglos asociativos asignan valores a claves de tipo cadena y requieren Bash 4 o posterior, por lo que no están disponibles en el antiguo Bash 3.2 que incluye macOS.
langs=("bash" "python" "go")
langs+=("rust")
echo "${langs[0]}" # bash
echo "${#langs[@]}" # 4
for l in "${langs[@]}"; do echo "- $l"; done
# associative array (Bash 4 or later)
declare -A port
port[http]=80
port[https]=443
echo "${port[https]}" # 443
for key in "${!port[@]}"; do
echo "$key -> ${port[$key]}"
doneEscribe siempre "${array[@]}" entre comillas para recorrer los elementos; así cada elemento se mantiene como una sola palabra aunque contenga espacios.
Tuberías y redirección
La característica distintiva de Bash es componer programas pequeños. Una tubería | envía la salida de un comando a la entrada del siguiente. Las redirecciones envían la salida a archivos o leen la entrada de ellos. El descriptor de archivo 1 es la salida estándar y el 2 es el error estándar.
ls -l > files.txt # write stdout to a file (overwrite)
echo "more" >> files.txt # append
grep error app.log 2> /dev/null # discard error messages
make > build.log 2>&1 # stdout and stderr to the same file
# count the 3 most common words in a file
tr -cs '[:alpha:]' '\n' < book.txt | tr '[:upper:]' '[:lower:]' |
sort | uniq -c | sort -rn | head -n 3Manejo de errores y scripts seguros
De forma predeterminada, Bash sigue adelante después de que un comando falla. Muchos scripts empiezan con set -euo pipefail: -e termina ante un error no tratado, -u considera errores las variables no definidas y -o pipefail hace que una tubería falle si falla cualquier comando de ella. trap ejecuta código de limpieza cuando el script termina.
#!/usr/bin/env bash
set -euo pipefail # stop on errors, unset vars, pipe failures
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT # clean up when the script exits
if ! grep -q "ready" status.txt; then
echo "status is not ready" >&2 # errors go to stderr
exit 1
fi
cp config.txt "$tmp" || { echo "copy failed" >&2; exit 2; }
echo "ok"Usa || para tratar un fallo en una sola línea y && para ejecutar un comando solo si el anterior tuvo éxito. set -e tiene excepciones (por ejemplo, los comandos dentro de condiciones if no lo activan), así que comprueba de forma explícita los comandos importantes.
Un script pequeño y completo: copias de seguridad con fecha
Este script archiva un directorio en un archivo .tar.gz con marca de tiempo y conserva solo los cinco archivos más recientes. Usa argumentos, valores predeterminados, pruebas, sustitución de comandos y tuberías.
#!/usr/bin/env bash
# backup.sh: copy a directory into a dated .tar.gz archive
set -euo pipefail
usage() { echo "usage: $0 SOURCE_DIR [DEST_DIR]" >&2; exit 1; }
[[ $# -ge 1 ]] || usage
src="$1"
dest="${2:-$HOME/backups}"
[[ -d "$src" ]] || { echo "not a directory: $src" >&2; exit 1; }
mkdir -p "$dest"
stamp=$(date +%Y%m%d-%H%M%S)
archive="$dest/$(basename "$src")-$stamp.tar.gz"
tar -czf "$archive" -C "$(dirname "$src")" "$(basename "$src")"
size=$(du -h "$archive" | cut -f1)
echo "Created $archive ($size)"
# keep only the 5 newest archives for this source
ls -1t "$dest"/"$(basename "$src")"-*.tar.gz | tail -n +6 | while read -r old; do
rm -f -- "$old"
echo "Removed $old"
done./backup.sh ~/projects/site
# Created /home/ann/backups/site-20261031-093000.tar.gz (2.1M)Siguientes pasos
- El GNU Bash Reference Manual, la documentación oficial.
man bashyhelp(por ejemplohelp read) en tu terminal.- ShellCheck, una herramienta de análisis estático que explica los errores comunes en los scripts.
- La especificación de la shell POSIX si necesitas scripts que también funcionen en
sh,dasho BusyBox.
Preguntas frecuentes
¿Cuál es la diferencia entre Bash y sh?
sh es el estándar de la shell POSIX, implementado por programas como dash. Bash es compatible con POSIX sh, pero añade características como [[ ]], arreglos, (( )) y la expansión de llaves.
¿Cuál es la diferencia entre Bash y zsh?
Ambos son compatibles para la mayoría de los comandos cotidianos. zsh añade funciones interactivas y es la shell predeterminada en macOS; los scripts escritos con un shebang de bash siguen ejecutándose con Bash.
¿Por qué mi script dice permission denied?
El archivo no es ejecutable. Ejecuta chmod +x script.sh o inícialo con bash script.sh.
¿Cómo paso argumentos a un script de Bash?
Escríbelos después del nombre del script, como en ./script.sh one two. Dentro del script son $1 y $2, $@ los contiene todos y $# es su número.
¿Debo escribir programas grandes en Bash?
Bash es ideal para unir comandos. Cuando un script se vuelve complejo, necesita estructuras de datos o un manejo de errores cuidadoso, un lenguaje como Python suele ser más fácil de mantener.