Lo que aprenderas en este tutorial
- Estructura basica de scripts de shell
- Uso de variables
- Condicionales (sentencia if)
- Bucles (for, while)
- Scripts de automatizacion practicos
¿Que es un script de shell? ¿Por que aprenderlo?
El nacimiento de los scripts de shell
La historia de los scripts de shell se remonta a 1971 con Thompson shell (sh). En 1979, Stephen Bourne desarrollo Bourne Shell (sh), que se convirtio en la base de los scripts de shell modernos.
En 1989, Brian Fox desarrollo Bash (Bourne Again Shell) como parte del proyecto GNU. Bash es actualmente el shell mas utilizado.
“Los scripts de shell son la forma mas conveniente de automatizar tareas repetitivas” — Sabiduria ancestral del entorno Unix
¿Por que aprender scripts de shell?
- Automatizacion: Automatiza tareas repetitivas y ahorra tiempo
- Administracion de sistemas: Esencial para gestion de servidores, despliegues y backups
- CI/CD: Ampliamente utilizado en GitHub Actions, Jenkins, etc.
- Portabilidad: Funciona en casi todos los sistemas Unix
Bash y otros shells
| Shell | Caracteristicas | Uso principal |
|---|---|---|
| bash | El mas extendido. Compatible con POSIX + extensiones | Scripts generales |
| sh | Estandar POSIX. Funcionalidad minima | Scripts con enfoque en portabilidad |
| zsh | Compatible con bash + potente autocompletado | Shell interactivo |
| fish | Amigable para el usuario | Shell interactivo |
| dash | Ligero y rapido | Scripts del sistema |
Mejores practicas: Usa
#!/bin/shcuando la portabilidad es importante, y#!/bin/bashcuando uses caracteristicas especificas de Bash.
Tu primer script de shell
Comencemos con “Hello World”.
hello.sh
#!/bin/bash
# Este es tu primer script de shell
echo "Hello, World!"
Metodo de ejecucion
# Otorgar permisos de ejecucion
chmod +x hello.sh
# Ejecutar
./hello.sh
# O, especificar bash explicitamente
bash hello.sh
Sobre el Shebang
La primera linea #!/bin/bash se llama shebang y especifica que interprete usara para ejecutar este script.
#!/bin/bash # Ejecutar con bash
#!/bin/sh # Ejecutar con POSIX sh (alta portabilidad)
#!/usr/bin/env bash # Buscar bash en PATH y ejecutar (recomendado)
Documentacion oficial: GNU Bash Manual
Uso de variables
Variables basicas
#!/bin/bash
# Definicion de variables (¡no pongas espacios alrededor del =!)
name="Taro"
age=25
# Referencia de variables
echo "Nombre: $name"
echo "Edad: ${age} anos"
# Asignar resultado de comando a variable
current_date=$(date +%Y-%m-%d)
echo "Fecha de hoy: $current_date"
# Sintaxis antigua (backticks) - no recomendada
old_style=`date +%Y-%m-%d`
Error comun: Espacios
# Incorrecto (error si hay espacios)
name = "Taro" # command not found: name
name= "Taro" # Variable vacia ejecutando comando "Taro"
# Correcto
name="Taro"
Alcance de variables
#!/bin/bash
# Variable global
global_var="I am global"
function example() {
# Variable local (solo valida dentro de la funcion)
local local_var="I am local"
echo "$local_var"
echo "$global_var"
}
example
echo "$local_var" # Vacio (no visible fuera de la funcion)
Variables especiales
#!/bin/bash
echo "Nombre del script: $0"
echo "Primer argumento: $1"
echo "Segundo argumento: $2"
echo "Numero de argumentos: $#"
echo "Todos los argumentos: $@"
echo "Todos los argumentos (cadena): $*"
echo "Estado de salida del comando anterior: $?"
echo "ID del proceso actual: $$"
echo "PID del proceso en segundo plano: $!"
Ejemplo de ejecucion:
$ ./script.sh arg1 arg2 arg3
Nombre del script: ./script.sh
Primer argumento: arg1
Segundo argumento: arg2
Numero de argumentos: 3
Todos los argumentos: arg1 arg2 arg3
Variables de entorno
# Referencia de variables de entorno
echo "Directorio home: $HOME"
echo "Nombre de usuario: $USER"
echo "Directorio actual: $PWD"
echo "Shell: $SHELL"
echo "Path: $PATH"
# Configurar variable de entorno (heredada por subprocesos)
export MY_VAR="some value"
# Configurar variable de entorno temporalmente y ejecutar comando
DEBUG=true ./my_script.sh
Diferencias entre comillas
name="World"
# Comillas dobles: las variables se expanden
echo "Hello, $name" # Hello, World
# Comillas simples: salida literal
echo 'Hello, $name' # Hello, $name
# Escapar con backslash
echo "Hello, \$name" # Hello, $name
Condicionales (sentencia if)
Sintaxis basica
#!/bin/bash
age=20
if [ $age -ge 20 ]; then
echo "Es adulto"
elif [ $age -ge 13 ]; then
echo "Es adolescente"
else
echo "Es nino"
fi
Tipos de sintaxis de prueba
# [ ] - Sintaxis test clasica (compatible con POSIX)
if [ $a -eq $b ]; then
# [[ ]] - Sintaxis extendida de Bash (recomendada)
if [[ $a == $b ]]; then
# (( )) - Evaluacion aritmetica
if (( a > b )); then
Mejores practicas: Se recomienda
[[ ]]cuando uses Bash. Permite coincidencia de patrones y expresiones regulares.
Operadores de comparacion numerica
| Operador | Significado | Ejemplo |
|---|---|---|
-eq | Igual (equal) | [ $a -eq $b ] |
-ne | No igual (not equal) | [ $a -ne $b ] |
-lt | Menor que (less than) | [ $a -lt $b ] |
-le | Menor o igual (less or equal) | [ $a -le $b ] |
-gt | Mayor que (greater than) | [ $a -gt $b ] |
-ge | Mayor o igual (greater or equal) | [ $a -ge $b ] |
Comparacion de cadenas
#!/bin/bash
str="hello"
# Comparacion de cadenas (¡es importante encerrar entre comillas!)
if [ "$str" = "hello" ]; then
echo "Las cadenas coinciden"
fi
# Verificar cadena vacia
if [ -z "$str" ]; then
echo "La cadena esta vacia"
fi
# Verificar si no esta vacia
if [ -n "$str" ]; then
echo "La cadena no esta vacia"
fi
# Coincidencia de patrones usando [[ ]]
if [[ "$str" == h* ]]; then
echo "Comienza con h"
fi
# Coincidencia de expresiones regulares (Bash 3.0 o posterior)
if [[ "$str" =~ ^[a-z]+$ ]]; then
echo "Solo letras minusculas"
fi
Verificacion de archivos y directorios
#!/bin/bash
# Verificar existencia de archivo
if [ -f "config.txt" ]; then
echo "config.txt existe"
fi
# Verificar existencia de directorio
if [ -d "logs" ]; then
echo "El directorio logs existe"
fi
# Existe archivo o directorio
if [ -e "path" ]; then
echo "path existe"
fi
# Legible
if [ -r "file.txt" ]; then
echo "Es legible"
fi
# Escribible
if [ -w "file.txt" ]; then
echo "Es escribible"
fi
# Ejecutable
if [ -x "script.sh" ]; then
echo "Es ejecutable"
fi
# El archivo no esta vacio
if [ -s "file.txt" ]; then
echo "El archivo no esta vacio"
fi
Operadores logicos
# AND
if [ $a -gt 0 ] && [ $a -lt 10 ]; then
echo "0 < a < 10"
fi
# OR
if [ $a -eq 0 ] || [ $a -eq 1 ]; then
echo "a is 0 or 1"
fi
# NOT
if [ ! -f "file.txt" ]; then
echo "file.txt does not exist"
fi
# En [[ ]] puedes usar && y ||
if [[ $a -gt 0 && $a -lt 10 ]]; then
echo "0 < a < 10"
fi
Sentencia case
La sentencia case es conveniente para multiples condiciones:
#!/bin/bash
fruit="apple"
case $fruit in
apple)
echo "Es una manzana"
;;
banana|orange)
echo "Es un platano o naranja"
;;
*)
echo "Fruta desconocida"
;;
esac
Bucles
Bucle for
#!/bin/bash
# Procesar lista en orden
for fruit in apple banana orange; do
echo "Fruta: $fruit"
done
# Rango numerico (extension de Bash)
for i in {1..5}; do
echo "Conteo: $i"
done
# Especificar paso
for i in {0..10..2}; do
echo "Par: $i"
done
# Estilo C del for
for ((i=0; i<5; i++)); do
echo "Index: $i"
done
# Procesar archivos
for file in *.txt; do
echo "Archivo: $file"
done
# Procesar salida de comando
for user in $(cat users.txt); do
echo "Usuario: $user"
done
Bucle while
#!/bin/bash
count=1
while [ $count -le 5 ]; do
echo "Conteo: $count"
count=$((count + 1))
done
# Leer archivo linea por linea (patron recomendado)
while IFS= read -r line; do
echo "Linea: $line"
done < input.txt
# Bucle infinito
while true; do
echo "Press Ctrl+C to stop"
sleep 1
done
Bucle until
#!/bin/bash
count=1
until [ $count -gt 5 ]; do
echo "Conteo: $count"
count=$((count + 1))
done
Control de bucles
# Salir del bucle con break
for i in {1..10}; do
if [ $i -eq 5 ]; then
break
fi
echo $i
done
# Saltar a la siguiente iteracion con continue
for i in {1..5}; do
if [ $i -eq 3 ]; then
continue
fi
echo $i
done
Definicion de funciones
#!/bin/bash
# Definicion de funcion (dos formas)
function greet() {
local name=$1
echo "Hola, ${name}!"
}
# O
greet2() {
echo "Hello, $1!"
}
# Llamar a la funcion
greet "Taro"
greet2 "World"
# Valor de retorno (estado de salida)
is_even() {
if (( $1 % 2 == 0 )); then
return 0 # Exito (par)
else
return 1 # Fallo (impar)
fi
}
if is_even 4; then
echo "4 es par"
fi
# Usar echo para retornar valores
add() {
echo $(( $1 + $2 ))
}
result=$(add 3 5)
echo "3 + 5 = $result"
Practica: Script de backup
Crearemos un script de backup practico usando lo que hemos aprendido.
backup.sh
#!/bin/bash
#
# Script de backup automatico
# Uso: ./backup.sh [source_dir] [backup_dir]
#
set -euo pipefail # Terminar inmediatamente en caso de error
# Configuracion
SOURCE_DIR="${1:-./src}"
BACKUP_DIR="${2:-./backups}"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_NAME="backup_$DATE.tar.gz"
RETENTION_DAYS=7
# Funcion de log
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
# Verificar/crear directorio de backup
if [ ! -d "$BACKUP_DIR" ]; then
log "Creando directorio de backup..."
mkdir -p "$BACKUP_DIR"
fi
# Verificar directorio fuente
if [ ! -d "$SOURCE_DIR" ]; then
log "Error: $SOURCE_DIR no encontrado"
exit 1
fi
# Ejecutar backup
log "Iniciando backup: $SOURCE_DIR -> $BACKUP_DIR/$BACKUP_NAME"
tar -czf "$BACKUP_DIR/$BACKUP_NAME" "$SOURCE_DIR"
if [ $? -eq 0 ]; then
log "Completado: $BACKUP_DIR/$BACKUP_NAME"
log "Tamano: $(du -h "$BACKUP_DIR/$BACKUP_NAME" | cut -f1)"
else
log "Error: El backup ha fallado"
exit 1
fi
# Eliminar backups antiguos
log "Eliminando backups antiguos (mas de ${RETENTION_DAYS} dias)..."
deleted=$(find "$BACKUP_DIR" -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete -print | wc -l)
log "Archivos eliminados: $deleted"
log "¡Todo completado!"
Mejores practicas para scripts
-
Usar
set -euo pipefail-e: Terminar inmediatamente en caso de error-u: Tratar variables no definidas como error-o pipefail: Detectar errores dentro de pipes
-
Siempre encerrar variables entre comillas:
"$variable" -
Usar funciones: Mejora la reutilizacion y legibilidad
-
Incluir logs: Para depuracion y monitoreo
Manejo de errores
#!/bin/bash
# Definir procesamiento en caso de error con trap
cleanup() {
echo "Ejecutando limpieza..."
# Eliminar archivos temporales, etc.
}
trap cleanup EXIT # Ejecutar al terminar el script
trap 'echo "Ocurrio un error: linea $LINENO"' ERR
# Si quieres ignorar errores
command_that_might_fail || true
# Procesamiento alternativo en caso de error
config_file="config.txt"
if [ -f "$config_file" ]; then
source "$config_file"
else
echo "Archivo de configuracion no encontrado. Usando valores por defecto."
default_value="fallback"
fi
Consejos de depuracion
# Ejecutar script completo en modo debug
bash -x script.sh
# Habilitar debug dentro del script
set -x # Iniciar debug
# ... codigo a depurar ...
set +x # Terminar debug
# Terminar inmediatamente en caso de error (recomendado)
set -e
# Tratar variables no definidas como error (recomendado)
set -u
# Detectar errores en pipes (recomendado)
set -o pipefail
# Combinacion comun
set -euo pipefail
Analisis estatico con ShellCheck
ShellCheck es una herramienta de analisis estatico que detecta problemas en scripts de shell.
# Instalacion
# macOS
brew install shellcheck
# Ubuntu/Debian
sudo apt install shellcheck
# Uso
shellcheck script.sh
Ejemplos de problemas que ShellCheck detecta:
# Advertencia: falta de comillas en variable
echo $USER # SC2086: Double quote to prevent globbing
# Corregido
echo "$USER"
El uso de ShellCheck tambien se recomienda en Google Shell Style Guide.
Patrones comunes
Validacion de argumentos
#!/bin/bash
if [ $# -lt 2 ]; then
echo "Uso: $0 <source> <destination>"
exit 1
fi
source=$1
destination=$2
Configurar valores por defecto
# Usar valor por defecto si la variable no esta definida o esta vacia
name="${1:-World}"
echo "Hello, $name"
# Usar valor por defecto solo si la variable no esta definida
name="${1-World}"
Creacion segura de archivos temporales
#!/bin/bash
# Usar mktemp (recomendado)
temp_file=$(mktemp)
temp_dir=$(mktemp -d)
# Eliminar al salir
trap "rm -rf $temp_file $temp_dir" EXIT
echo "Archivo temporal: $temp_file"
echo "Directorio temporal: $temp_dir"
Obtener entrada del usuario
#!/bin/bash
# Leer entrada
read -p "Ingresa tu nombre: " name
echo "Hola, $name"
# Entrada de contrasena (oculta)
read -sp "Contrasena: " password
echo ""
# Con timeout
read -t 5 -p "Ingresa en 5 segundos: " input || echo "Tiempo agotado"
Siguientes pasos
Una vez que domines los fundamentos de scripts de shell, avanza a los siguientes pasos:
- Expresiones regulares y
grep,sed,awk - Ejecucion programada con cron jobs
- Automatizacion mas avanzada (Ansible, Makefile, etc.)
Enlaces de referencia
Documentacion oficial
- GNU Bash Manual - Referencia oficial de Bash
- POSIX Shell Command Language - Estandar POSIX
Guias de estilo y mejores practicas
- Google Shell Style Guide - Guia de estilo de scripts de shell de Google
- ShellCheck - Herramienta de analisis estatico para scripts de shell
Recursos de aprendizaje
- Bash Hackers Wiki - Tecnicas avanzadas de Bash
- explainshell.com - Ingresa un comando y obtiene explicacion
- Pure Bash Bible - Coleccion de recetas implementadas solo en Bash
Hojas de referencia
- Bash Scripting Cheat Sheet - Lista de sintaxis de uso comun
- Advanced Bash-Scripting Guide - Guia detallada de Bash (ingles)