Fundamentos de los scripts de shell

ShellBeginner
Practicar Ahora

Introducción

Un script de shell almacena comandos en un archivo para que una tarea pueda repetirse de forma coherente. Los scripts fiables para principiantes especifican claramente su intérprete, protegen las expansiones de variables con comillas, validan la entrada antes de actuar y devuelven un estado de salida que otros comandos pueden interpretar.

En este laboratorio crearás pequeños scripts Bash de forma progresiva. Ejecutarás scripts mediante Bash y como archivos ejecutables, usarás variables y argumentos posicionales, leerás entradas interactivas, crearás ramificaciones con if y comprobaciones de archivos, elegirás códigos de salida, iterarás con for y combinarás estos conceptos en un sencillo resumen de registros.

Crea y ejecuta tu primer script

En este paso crearás un script Bash, lo ejecutarás mediante un intérprete y lo convertirás en un archivo ejecutable directamente.

Accede al espacio de trabajo del laboratorio:

cd /home/labex/project/shell-lab

Crea hello.sh con un documento aquí. La primera línea es un shebang que selecciona Bash cuando el archivo se ejecuta directamente:

cat > hello.sh <<'EOF'
#!/bin/bash

echo "Hello from a Bash script"
pwd
EOF

Inspecciona el archivo antes de ejecutarlo:

cat hello.sh

Pasa el archivo explícitamente a Bash. Esto funciona incluso antes de añadir permisos de ejecución:

bash hello.sh

Añade permisos de ejecución y comprueba el modo:

chmod u+x hello.sh
ls -l hello.sh

Ejecuta el archivo desde el directorio actual. ./ proporciona una ruta; normalmente el shell no busca automáticamente en el directorio actual:

./hello.sh

Usa variables y comillas seguras

En este paso asignarás variables, las expandirás y comprobarás por qué las comillas dobles protegen los valores que contienen espacios.

Crea un script con dos variables:

cat > variables.sh <<'EOF'
#!/bin/bash

course="Linux for Noobs"
workspace="/home/labex/project/shell-lab"

echo "Course: $course"
echo "Workspace: $workspace"
printf 'Quoted argument count: %s\n' "$#"
printf 'First argument: %s\n' "${1:-not provided}"
EOF

En una asignación del shell no debe haber espacios alrededor de =. Las comillas dobles permiten expandir $course y, al mismo tiempo, conservar Linux for Noobs como un único valor.

Ejecuta el script con un argumento que contiene espacios:

bash variables.sh "beginner learner"

La variable especial $# indica el número de argumentos posicionales y $1 corresponde al primer argumento. ${1:-not provided} proporciona un valor alternativo cuando el argumento no está presente.

Guarda la salida para inspeccionarla:

bash variables.sh "beginner learner" > variable-output.txt
cat variable-output.txt

Valida los argumentos posicionales

En este paso exigirás un argumento, mostrarás instrucciones de uso y devolverás distintos estados de salida para indicar errores y ejecuciones correctas.

Crea greet-argument.sh:

cat > greet-argument.sh <<'EOF'
#!/bin/bash

if [ "$#" -ne 1 ]; then
  echo "Usage: $0 NAME" >&2
  exit 2
fi

name=$1
echo "Hello, $name"
exit 0
EOF

El comando de prueba [ ... ] comprueba si el número de argumentos no es igual a uno. >&2 envía el texto de uso al error estándar y exit 2 indica que el uso no es válido.

Ejecútalo sin argumentos y muestra inmediatamente $?, que contiene el estado de salida del comando anterior:

bash greet-argument.sh
echo "missing argument status: $?"

El estado esperado es 2. Ahora ejecútalo correctamente:

bash greet-argument.sh "Ada Lovelace"
echo "valid argument status: $?"

Guarda el saludo generado correctamente:

bash greet-argument.sh "Ada Lovelace" > argument-greeting.txt

Lee entradas interactivas

En este paso solicitarás un dato al usuario, leerás una línea de forma segura y controlarás la entrada vacía.

Crea ask-name.sh:

cat > ask-name.sh <<'EOF'
#!/bin/bash

read -r -p "Enter your name: " name

if [ -z "$name" ]; then
  echo "A name is required" >&2
  exit 1
fi

echo "Welcome, $name"
EOF

read -r lee una línea sin tratar las barras inversas de forma especial. -p muestra un mensaje y -z comprueba si el valor entre comillas está vacío.

Ejecuta el script:

bash ask-name.sh

Cuando aparezca el mensaje, escribe:

Grace Hopper

Deberías ver Welcome, Grace Hopper. Para obtener un resultado guardado y reproducible, canaliza una línea de entrada al mismo script:

printf 'Grace Hopper\n' | bash ask-name.sh > interactive-greeting.txt
cat interactive-greeting.txt

Crea ramificaciones con comprobaciones de archivos

En este paso usarás if, elif y else, junto con comprobaciones de archivos, para clasificar una ruta.

Crea classify-path.sh:

cat > classify-path.sh <<'EOF'
#!/bin/bash

path=${1:-}

if [ -z "$path" ]; then
  echo "Usage: $0 PATH" >&2
  exit 2
elif [ -d "$path" ]; then
  echo "directory: $path"
elif [ -f "$path" ]; then
  echo "file: $path"
else
  echo "missing: $path" >&2
  exit 1
fi

exit 0
EOF

-d comprueba si se trata de un directorio y -f comprueba si es un archivo normal. El orden es importante: el uso no válido se gestiona antes de determinar el tipo de ruta.

Prueba los tres posibles resultados:

bash classify-path.sh logs
bash classify-path.sh notes.txt
bash classify-path.sh missing.txt
echo "missing path status: $?"

Guarda juntos los resultados correctos. && ejecuta el segundo comando únicamente si el primero termina correctamente:

bash classify-path.sh logs > path-results.txt && bash classify-path.sh notes.txt >> path-results.txt
cat path-results.txt

Procesa varios archivos con un bucle For

En este paso iterarás sobre archivos de registro y contarás los registros coincidentes en cada uno.

Crea count-errors.sh:

cat > count-errors.sh <<'EOF'
#!/bin/bash

log_dir=${1:-logs}

if [ ! -d "$log_dir" ]; then
  echo "Log directory not found: $log_dir" >&2
  exit 1
fi

for log_file in "$log_dir"/*.log; do
  error_count=$(grep -c '^ERROR ' "$log_file" || true)
  printf '%s errors=%s\n' "$(basename "$log_file")" "$error_count"
done
EOF

En cada iteración, el bucle asigna a log_file una de las rutas coincidentes. Las comillas de "$log_file" protegen los espacios, mientras que basename elimina el prefijo del directorio. grep -c cuenta las líneas coincidentes; || true evita que la ausencia de coincidencias detenga un flujo de trabajo más amplio y estricto.

Ejecuta el bucle y guarda su salida:

bash count-errors.sh logs | tee error-counts.txt

Deberías ver un resultado por cada archivo .log.

Combina validación, bucles y estados de salida

En este paso crearás un script reutilizable que valide un directorio, resuma sus registros e indique claramente si la operación tuvo éxito o falló.

Crea summarize-logs.sh:

cat > summarize-logs.sh <<'EOF'
#!/bin/bash

input_dir=${1:-}
output_file=${2:-}

if [ -z "$input_dir" ] || [ -z "$output_file" ]; then
  echo "Usage: $0 INPUT_DIR OUTPUT_FILE" >&2
  exit 2
fi

if [ ! -d "$input_dir" ]; then
  echo "Input directory not found: $input_dir" >&2
  exit 1
fi

: > "$output_file"

for log_file in "$input_dir"/*.log; do
  lines=$(wc -l < "$log_file")
  errors=$(grep -c '^ERROR ' "$log_file" || true)
  printf '%s lines=%s errors=%s\n' "$(basename "$log_file")" "$lines" "$errors" >> "$output_file"
done

echo "Wrote summary to $output_file"
exit 0
EOF

El comando : > "$output_file" crea el archivo de destino si no existe o vacía su contenido de forma segura antes del bucle. Ejecuta el script con los dos argumentos obligatorios:

chmod u+x summarize-logs.sh
./summarize-logs.sh logs log-summary.txt
echo "summary status: $?"
cat log-summary.txt

Prueba la ruta de error con un directorio inexistente:

./summarize-logs.sh missing log-summary.txt
echo "missing directory status: $?"

El estado de éxito debería ser 0, mientras que el estado correspondiente al directorio inexistente debería ser 1.

Resumen

Has creado y ejecutado scripts Bash, has usado shebangs y permisos, has expandido variables entre comillas, has leído entradas posicionales e interactivas y has devuelto estados de salida significativos. También has creado ramificaciones con if/elif/else, comprobado archivos y directorios, iterado sobre registros con for y combinado estos conceptos en un script reutilizable para generar resúmenes.

Estos fundamentos hacen que la automatización posterior sea más fácil de leer, más segura de volver a ejecutar y más sencilla de evaluar para otros comandos o tareas programadas.