Curso Bash

Intérprete de comandos para sistemas Unix y Linux.

Certificado profesional
Bash
Bash
Lenguaje
15 horas
6 módulos
21 lecciones
29 ejercicios
Chet Ramey
Documentación oficial
Actualizado: 10/05/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Descripción del curso Bash

Bash (Bourne Again SHell) es un intérprete de comandos y lenguaje de scripting que actúa como interfaz entre el usuario y el sistema operativo Unix/Linux. Desarrollada como parte del Proyecto GNU, es una evolución mejorada de la shell Bourne original (sh) que incorpora características de otras shells como Korn shell (ksh) y C shell (csh).

Como shell predeterminada en la mayoría de distribuciones Linux, macOS (hasta Catalina) y disponible en Windows a través de WSL (Windows Subsystem for Linux), Bash se ha convertido en una herramienta esencial para administradores de sistemas, desarrolladores y usuarios avanzados.

Cuando abres una terminal, generalmente interactúas con Bash a través de un prompt que muestra información como tu nombre de usuario, el host y el directorio actual:

usuario@host:~/documentos$

El símbolo $ indica un usuario regular, mientras que # aparece cuando trabajas como superusuario (root).

Para verificar qué shell estás utilizando:

echo $SHELL

Para conocer la versión de Bash:

bash --version

Sintaxis básica

La estructura general de un comando en Bash es:

comando [opciones] [argumentos]

Por ejemplo:

ls -la /home/usuario

Donde:

  • ls es el comando que lista archivos
  • -la son las opciones (l: formato largo, a: mostrar archivos ocultos)
  • /home/usuario es el argumento (el directorio a listar)

Puedes ejecutar varios comandos en una misma línea utilizando diferentes separadores:

Punto y coma (;): Ejecuta los comandos secuencialmente independientemente del resultado.

echo "Primero"; echo "Segundo"

AND lógico (&&): El segundo comando se ejecuta solo si el primero tiene éxito.

mkdir directorio && cd directorio

OR lógico (||): El segundo comando se ejecuta solo si el primero falla.

grep "patrón" archivo.txt || echo "Patrón no encontrado"

Los comentarios en Bash comienzan con el símbolo #:

# Este es un comentario
echo "Hola mundo" # Este es un comentario al final de una línea

Variables

En Bash, las variables se declaran sin especificar tipo:

nombre="Ana"
edad=25
es_estudiante=true

Reglas importantes:

  • No debe haber espacios antes ni después del signo igual (=)
  • Los nombres de variables pueden contener letras, números y guiones bajos, pero no pueden comenzar con un número
  • Las variables son sensibles a mayúsculas y minúsculas

Para acceder al valor de una variable, se usa el prefijo $:

echo "Hola, $nombre. Tienes $edad años."

También puedes usar la forma ${variable}, que es útil para desambiguar:

echo "Hola, ${nombre}s" # Para mostrar "Hola, Anas"

Bash proporciona varias variables especiales:

  • $0: Nombre del script
  • $1, $2, etc.: Argumentos posicionales
  • $#: Número de argumentos
  • $@: Todos los argumentos como palabras separadas
  • $*: Todos los argumentos como una sola palabra
  • $?: Código de salida del último comando (0 indica éxito)
  • $$: PID (Process ID) de la shell actual
  • $!: PID del último proceso en segundo plano

Las variables de entorno afectan el comportamiento del sistema y los programas:

echo $HOME      # Directorio principal del usuario
echo $PATH      # Rutas donde se buscan ejecutables
echo $USER      # Nombre de usuario actual

Para crear una variable de entorno que esté disponible para los procesos hijo:

export MI_VARIABLE="valor"

Arrays (matrices)

Bash soporta arrays indexados (desde 0) y, en versiones más recientes, arrays asociativos.

Arrays indexados:

# Declaración e inicialización
frutas=("manzana" "naranja" "plátano")

# Acceso a elementos
echo ${frutas[0]}     # manzana

# Añadir elemento
frutas+=("uva")

# Mostrar todos los elementos
echo ${frutas[@]}

# Número de elementos
echo ${#frutas[@]}

Arrays asociativos (desde Bash 4.0):

# Declaración (requerida para arrays asociativos)
declare -A colores

# Asignación
colores[rojo]="#FF0000"
colores[verde]="#00FF00"
colores[azul]="#0000FF"

# Acceso
echo ${colores[rojo]}

# Todas las claves
echo ${!colores[@]}

Operadores

Para realizar operaciones aritméticas, Bash ofrece varias sintaxis:

# La forma recomendada
resultado=$((5 + 3))

Operadores disponibles:

  • +: Suma
  • -: Resta
  • *: Multiplicación
  • /: División (entera)
  • %: Módulo (resto)
  • **: Exponenciación
echo $((10 + 5))    # 15
echo $((10 - 5))    # 5
echo $((10 * 5))    # 50
echo $((10 / 3))    # 3 (división entera)
echo $((10 % 3))    # 1 (resto)
echo $((2 ** 3))    # 8 (potencia)

Para incremento y decremento:

((variable++))     # Incremento después de usar
((++variable))     # Incremento antes de usar

Operadores de comparación numérica (dentro de (( ))):

if ((a < b)); then
    echo "a es menor que b"
fi
  • ==: Igual
  • !=: No igual
  • <: Menor que
  • <=: Menor o igual
  • >: Mayor que
  • >=: Mayor o igual

Operadores de comparación de cadenas (dentro de [[ ]]):

if [[ "$a" == "$b" ]]; then
    echo "Las cadenas son iguales"
fi
  • ==: Igual
  • !=: No igual
  • <: Menor en orden lexicográfico
  • >: Mayor en orden lexicográfico
  • -z: Verdadero si la cadena está vacía
  • -n: Verdadero si la cadena no está vacía

Operadores para comprobación de archivos:

if [[ -f "$archivo" ]]; then
    echo "Es un archivo regular"
fi
  • -f: Es un archivo regular
  • -d: Es un directorio
  • -e: Existe (archivo o directorio)
  • -r: Tiene permiso de lectura
  • -w: Tiene permiso de escritura
  • -x: Tiene permiso de ejecución
  • -s: Tamaño mayor que cero
  • -L: Es un enlace simbólico

Estructuras de control

La estructura if:

if [[ condición ]]; then
    # comandos
elif [[ otra_condición ]]; then
    # otros comandos
else
    # comandos por defecto
fi

Nota: Los espacios dentro de [[ ]] son importantes.

Estructura case:

case $variable in
    patrón1)
        # comandos
        ;;
    patrón2|patrón3)
        # comandos para múltiples patrones
        ;;
    *)
        # caso por defecto
        ;;
esac

Bucle for (varios formatos):

# Iteración sobre lista
for nombre in Juan Ana Pedro; do
    echo "Hola, $nombre"
done

# Rango numérico
for i in {1..5}; do
    echo "Número: $i"
done

# Sintaxis estilo C
for ((i=0; i<5; i++)); do
    echo "Iteración $i"
done

# Iteración sobre archivos
for archivo in *.txt; do
    echo "Procesando $archivo"
done

Bucle while:

contador=0
while [[ $contador -lt 5 ]]; do
    echo "Contador: $contador"
    ((contador++))
done

Bucle until (continúa hasta que la condición sea verdadera):

contador=0
until [[ $contador -ge 5 ]]; do
    echo "Contador: $contador"
    ((contador++))
done

Control de bucles:

for i in {1..10}; do
    if [[ $i -eq 5 ]]; then
        continue    # Salta a la siguiente iteración
    fi
    
    if [[ $i -eq 8 ]]; then
        break       # Sale del bucle
    fi
    
    echo "Número: $i"
done

Funciones

Definición y llamada de funciones:

# Definición con la palabra clave "function"
function saludar {
    echo "Hola, $1!"
}

# Forma alternativa
saludar_alternativa() {
    echo "Hola, $1!"
}

# Llamada
saludar "Juan"

Las funciones en Bash no devuelven valores como en otros lenguajes, pero existen varias técnicas:

# Usando código de salida
function es_par {
    if (( $1 % 2 == 0 )); then
        return 0    # verdadero en Bash
    else
        return 1    # falso en Bash
    fi
}

# Verificación
if es_par 4; then
    echo "Es par"
fi

# Usando echo y captura
function suma {
    echo $(( $1 + $2 ))
}

resultado=$(suma 5 3)
echo "La suma es: $resultado"

Las variables en Bash son globales por defecto, a menos que se declaren como local:

variable_global="Global"

function prueba_alcance {
    local variable_local="Local"
    echo "Dentro: global=$variable_global, local=$variable_local"
}

prueba_alcance
echo "Fuera: global=$variable_global"
echo "Intentando acceder a variable local: $variable_local" # Será vacío

Entrada y salida

Para obtener entrada del usuario:

# Lectura básica
echo "Introduce tu nombre:"
read nombre
echo "Hola, $nombre"

# Con prompt incorporado
read -p "Introduce tu edad: " edad
echo "Tienes $edad años"

# Entrada silenciosa (para contraseñas)
read -sp "Contraseña: " password
echo -e "\nContraseña recibida"

Redirección de entrada/salida:

# Redirigir salida estándar a archivo (sobrescribir)
comando > archivo.txt

# Redirigir y anexar salida estándar a archivo
comando >> archivo.txt

# Redirigir error estándar
comando 2> errores.log

# Redirigir ambos (salida y error) a archivos diferentes
comando > salida.txt 2> errores.log

# Redirigir ambos al mismo archivo
comando > archivo.txt 2>&1

# Forma moderna para redirigir ambos
comando &> archivo.txt

# Descartar salida
comando > /dev/null

Las tuberías (pipes) permiten conectar la salida de un comando con la entrada de otro:

# Filtrar resultados
ls -la | grep ".txt"

# Encadenar múltiples comandos
cat archivo.txt | tr '[:lower:]' '[:upper:]' | sort > resultado.txt

# Contar líneas/palabras/caracteres
cat archivo.txt | wc -l

Procesamiento de archivos

Lectura de archivos:

# Línea por línea
while IFS= read -r linea; do
    echo "Leído: $linea"
done < archivo.txt

# Todo el archivo de una vez
contenido=$(<archivo.txt)
echo "$contenido"

# Usando IFS para CSV
while IFS=, read -r campo1 campo2 campo3; do
    echo "Campo 1: $campo1, Campo 2: $campo2, Campo 3: $campo3"
done < datos.csv

Escritura en archivos:

# Sobrescribir
echo "Nuevo contenido" > archivo.txt

# Añadir
echo "Contenido adicional" >> archivo.txt

# Usando redirección heredoc
cat << EOF > archivo.txt
Línea 1
Línea 2
Línea con variable: $variable
EOF

# Here-doc con variables no expandidas
cat << 'EOF' > archivo.txt
Línea con $variable sin expandir
EOF

Expansiones y sustituciones

Expansión de variables:

nombre="Juan"
echo "${nombre}"            # Expansión simple
echo "${nombre}s"           # Desambiguación
echo "${nombre:-defecto}"   # Valor por defecto si no existe o está vacía
echo "${nombre:=defecto}"   # Asignar valor por defecto si no existe o está vacía
echo "${nombre:+alternativo}" # Valor alternativo si existe y no está vacía
echo "${nombre:?mensaje}"   # Error con mensaje si no existe o está vacía

Manipulación de cadenas:

cadena="Hola Mundo"
echo "${cadena:5}"       # "Mundo" (a partir del índice 5)
echo "${cadena:0:4}"     # "Hola" (4 caracteres desde el índice 0)
echo "${#cadena}"        # 10 (longitud de la cadena)

# Manipulación de patrones
archivo="imagen.jpg"
echo "${archivo%.jpg}"   # "imagen" (elimina .jpg del final)
echo "${archivo#imagen}" # ".jpg" (elimina imagen del principio)

# Sustitución
echo "${cadena/Mundo/Amigo}" # "Hola Amigo" (reemplaza primera coincidencia)
echo "${cadena//o/O}"       # "HOla MundO" (reemplaza todas las coincidencias)

# Transformaciones (Bash 4.0+)
echo "${cadena^^}"       # "HOLA MUNDO" (todo a mayúsculas)
echo "${cadena,,}"       # "hola mundo" (todo a minúsculas)

Expansión de comandos:

# Sintaxis moderna (recomendada)
fecha_actual=$(date)
echo "La fecha actual es: $fecha_actual"

# Anidamiento
echo "Archivos encontrados: $(find . -name "*.txt" | wc -l)"

# Cálculo aritmético
echo "Resultado: $((5 * $(wc -l < archivo.txt)))"

Expansión de llaves:

# Generación de secuencias
echo {1..5}        # "1 2 3 4 5"
echo {a..e}        # "a b c d e"
echo {1..10..2}    # "1 3 5 7 9" (paso de 2)

# Combinaciones
echo file{1,2,3}.txt  # "file1.txt file2.txt file3.txt"
echo archivo.{jpg,png,gif}    # "archivo.jpg archivo.png archivo.gif"

# Anidamiento
echo {a,b}{1,2}      # "a1 a2 b1 b2"

Control de procesos

Ejecución en segundo plano:

# Ejecutar comando en segundo plano
comando &

# Verificar trabajos en ejecución
jobs

# Traer trabajo a primer plano
fg %1

# Enviar trabajo a segundo plano (después de Ctrl+Z)
bg %1

Gestión de procesos:

# Listar procesos
ps aux
ps -ef

# Filtrar procesos
ps aux | grep "firefox"

# Matar proceso por PID
kill 1234

# Forzar terminación
kill -9 1234

# Matar proceso por nombre
pkill firefox
killall firefox

Ejecución condicional y señales:

# Capturar señal Ctrl+C (SIGINT)
trap "echo 'Operación cancelada'; exit" SIGINT

# Limpiar al salir
trap "echo 'Limpiando...'; rm -f /tmp/archivo_temp.$$" EXIT

# Ignorar señal
trap "" SIGTERM

# Restablecer comportamiento predeterminado
trap - SIGINT

Scripting avanzado

Estructura de un script:

#!/bin/bash
# Descripción: Este script hace X
# Autor: Tu nombre
# Fecha: YYYY-MM-DD

# Configuración
set -euo pipefail

# Constantes
readonly CONFIG_FILE="/etc/miapp.conf"
readonly MAX_INTENTOS=3

# Funciones
function log_mensaje() {
    local timestamp=$(date "+%Y-%m-%d %H:%M:%S")
    echo "[$timestamp] $1"
}

# Código principal
log_mensaje "Iniciando script"

# ... resto del script

log_mensaje "Script completado"
exit 0

Opciones de línea de comandos:

while getopts ":f:v:h" opt; do
  case $opt in
    f)
      archivo="$OPTARG"
      ;;
    v)
      nivel_verbosidad="$OPTARG"
      ;;
    h)
      echo "Uso: $0 [-f archivo] [-v nivel] [-h]"
      exit 0
      ;;
    \?)
      echo "Opción inválida: -$OPTARG" >&2
      exit 1
      ;;
    :)
      echo "La opción -$OPTARG requiere un argumento." >&2
      exit 1
      ;;
  esac
done

Manejo de errores:

# Salir al primer error
set -e

# Detectar uso de variables no definidas
set -u

# Propagar errores en tuberías
set -o pipefail

# Todo junto (recomendado para scripts robustos)
set -euo pipefail

# Función de error
function error_salida() {
    local linea=$1
    local mensaje=$2
    echo "Error en línea $linea: $mensaje" >&2
    exit 1
}

# Usar trap para capturar errores con información de línea
trap 'error_salida ${LINENO} "$BASH_COMMAND"' ERR

Inclusión de archivos:

# Cargar funciones o variables de otro archivo
source funciones.sh
# o
. funciones.sh

# Verificar si el archivo existe antes de incluirlo
if [[ -f config.sh ]]; then
    source config.sh
else
    echo "Archivo de configuración no encontrado" >&2
    exit 1
fi

Buenas prácticas

Seguridad:

# Comillas en variables para evitar problemas con espacios
echo "Procesando archivo: \"$archivo\""

# Validar entrada antes de usar
if [[ ! $numero =~ ^[0-9]+$ ]]; then
    echo "Error: '$numero' no es un número válido" >&2
    exit 1
fi

# Evitar inyección de comandos
archivo_seguro=$(printf '%q' "$nombre_archivo")
eval "cp $archivo_seguro /destino/"

Portabilidad:

# Usar shebang con env para mayor portabilidad
#!/usr/bin/env bash

# Comprobar disponibilidad de comandos
if ! command -v jq &> /dev/null; then
    echo "Error: jq no está instalado" >&2
    exit 1
fi

# Establecer ruta predeterminada para comandos
PATH="/usr/local/bin:/usr/bin:/bin"

Eficiencia:

# Evitar llamadas externas innecesarias
if [[ "$cadena" == *"subcadena"* ]]; then  # Expansión de patrón nativa
    echo "Contiene subcadena"
fi

# Uso de parámetros nativos
echo "${#cadena}"  # en lugar de echo "$cadena" | wc -c

# Usar mapeos internos para transformaciones simples
echo "${cadena^^}" # en lugar de echo "$cadena" | tr '[:lower:]' '[:upper:]'

Depuración:

# Mostrar comandos al ejecutarse
set -x
comando1
comando2
set +x

# Solo activar depuración si se establece una variable
if [[ "${DEBUG:-}" == "true" ]]; then
    set -x
fi

# Ejecutar script con depuración
bash -x script.sh

Ejercicios de programación de Bash

Explorar más tecnologías

Descubre más tecnologías de programación y desarrollo de software

Alan Sastre - Autor del curso

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Bash es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.