Bash

De Astillas.net
Versión
GNU bash, version 3.2.39(1)-release
Página principal
http://www.gnu.org/software/bash/
Observaciones


Enlaces y referencias

Conceptos

Parámetros especiales

$0 Nombre del programa que está siendo ejecutado por bash.
$# Número de parámetros recibidos en la línea de órdenes
$* Lista de parámetros separados por el delimitador contenido en la variable IFS.
$@ Lista de parámetros entrecomillados (comillas dobles) individualmente.
$? Código de finalización del último programa ejecutado en primer plano.
$! Identificador de proceso (PID) del programa más reciente lanzado en segundo plano.

Testeos

De texto -z (sin contenido), =, !=, ...
Numéricos -eq, -gt, -lt, ...
Archivos -f (es un fichero regular), -d (es un directorio), -w (es modificable), ...

Asignación de valores

Bash puede efectuar algunos tests sobre los parámetros cuando se le asignan valores. Si se emplea el carácter dos puntos (:) se comprueba que el parámetro exista y que su valor no sea nulo. Si se omiten los dos puntos sólo se comprueba su existencia.

Tipo de asignación Descripción
${parameter:−word} Si el parámetro no existe o es nulo se emplea la expansión de word como resultado de la expresión pero el parámetro no se modifica. En caso contrario se emplea la expansión del parámetro.
${parameter:=word} Si el parámetro no existe o es nulo la expansión de word se asigna al parámetro (modificándolo). No puede emplearse para modificar parámetros posicionales o especiales.
${parameter:?word} Si el parámetro es nulo o no está definido se crea un mensaje con la expansión de word (o se usa un mensaje determinado si word no está presente) y se envía al canal de errores del proceso; si el shell no es interactivo, además, se termina su ejecución.

Si el parámetro tiene algún valor entonces se usa su expansión como resultado de la expresión.

${parameter:+word} Si el parámetro es nulo o no está definido la expresión no produce ningún resultado, no hay sustitución alguna. En caso contrario, que el parámetro tenga algún valor, se emplea la expansión de word.

Expansión de variables

${#foo} Longitud en caracteres del contenido de foo.
${foo:3:5} Extrae los caracteres 3º a 5º de la variable foo.
${foo:4} Extrae los caracteres 4º hasta el final de la variable foo.
${foo#STRING} Contenido de foo pero eliminando la coincidencia más pequeña del texto STRING buscando desde el comienzo.
${foo%STRING} Igual que el anterior pero búsqueda es desde el final.
${foo%%STRING} Contenido de foo pero eliminando la mayor coincidencia del texto STRING desde el final.
${foo##STRING} Igual que el anterior pero la coincidencia es desde el principio.
${foo/bar/baz} Contenido de foo pero sustituyendo la primera coincidencia del texto bar con el texto baz.
${foo//bar/baz}} Igual que el anterior pero sustituyendo todas las coincidencias.

Funciones

  • Para definir funciones se puede emplear cualquiera de las dos síntaxis:
function nombre 
{

}

nombre () {

}
  • Si la función está expresada en una única línea debe terminar obligatoriamente con un punto y coma:
fun () { echo "Esta es una funcion"; }
  • La definición de la función debe preceder a su uso.
  • El cuerpo de una función no puede estar vacío.
  • Una función no puede retornar valores más allá de cero (fallo) o uno (éxito). Para usar un resultado diferente en una expresión es necesario emplear algún truco como variables globales.

Expresiones aritméticas

Las operaciones aritméticas pueden llevarse a cabo mediante el programa expr con bastante facilidad y claridad.

Algunos ejemplos:

# Arithmetic Operators
# ---------- ---------

echo "Arithmetic Operators"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"

a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incrementing a variable)"

a=`expr 5 % 3`
# modulo
echo
echo "5 mod 3 = $a"

echo

Recetario

Depurando programas

Para depurar programas escritos en 'Bash' se pueden emplear los siguientes parámetros en la llamada al intérprete:

sh -n programa Verifica la síntaxis del programa sin ejecutarlo.
sh -v programa Imprime cada línea del programa justo antes de ejecutarla. Puede combinarse con -n para obtener una verificación sintáctica verbosa.
sh -x programa Imprime abreviado el resultado de cada línea del programa.

También es posible ejecutar algo en cada una de los pasos de ejecución si empleamos la directiva trap con la señal DEBUG:

#!/bin/bash
set -x 

# Muestra el contenido de la variable  
trap 'echo \$variable = $variable' DEBUG  

# o lee por la entrada estándar
trap read DEBUG

getopt

Existen dos mecanismos normalizados para analizar parámetros en el programa Bash. El primero es el programa externo getopt y el segundo es la función interna de Bash getopts (se añade una ese al final).

El programa externo es más versátil pero presenta mayor dificultad de uso. Su tarea consiste en reordenar los parámetros y proporcionar una lista de fácil tratamiento con los nombres de los parámetros seguidos del valor recibido.

Un ejemplo de llamada al programa externo:

# Recogemos la salida para poder comprobar el código de error 
args=`getopt -o nc: --long dry-run,config: -- "$@"`

# Si hay un error finalizamos ...
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Cargamos los valores en la lista de parámetros posicionales de Bash
eval set -- "$args"

# default values 
simulate=0

# y la recorremos tomando nota de cada uno de ellos 
for i
do
    case "$i" in 
    -c|--config) # archivo de configuración
        config_file=shift
        ;;
    -n|--dry-run) # simular el proceso 
        simulate=1
        ;;
    *) # lo que queda es lo extra 
       input_file=shift 
       output_file=shift
       ;;
    esac
done

La orden getopts es más cómoda ya que está integrada en el intérprete de órdenes pero está más limitado en su funcionalidad puesto que no maneja parámetros largos estilo GNU (--myoption) ni construcciones especiales estilo XFree86 (-myoption).

Además también cambia la forma de utilizarlo. El programa externo procesa todos los parámetros de una sóla vez mientras que la versión integrada trabaja cada vez que se la llama y debe estar integrada en el bucle:

# Valores por defecto 
simulate=0

# Procesado de todos los parámetros recibidos por el script 
while getopts ":c:n" flag
do
   # "flag" contiene el nombre del parámetro encontrado
   # OPTARG contiene el correspondiente valor 
   # OPTIND es el número del siguiente parámetro a evaluar
   case "$flag" in 
   c) config_file=$OPTARG ;;
   n) simulate=1 ;;
   \?) echo "Parámetro erróneo $OPTARG" >&2; exit 2 ;;
   :) echo "Parámetro incompleto $OPTARG" >&2; exit 2;;
   esac 
done

Si la lista de opciones comienza por el carácter dos puntos (:) se activa el modo silencioso y no se emiten mensajes de error. En su lugar cuando se encuentra un parámetro erróneo se asigna el carácter ? a la variable donde recogemos el parámetro (flag). Si el problema ha sido un parámetro incompleto se asigna el carácter dos puntos.

Además es necesario descartar a mano los parámetros empleados para tener acceso a lo que quede:

while getopts '...' flag 
do 
    # procesado 
done

# Descartamos todo lo que se ha utilizado 
shift $((OPTIND-1))

Funciones de registro de eventos

Basándome en la referencia arriba anotada se puede crear una pequeña librería de funciones que registren eventos en un programa bash.

Partiendo de una función básica como la siguiente, que toma dos valores externos como son el nivel de depuración ($DEBUG) y el archivo de registro ($LOG_FILE), tendríamos una herramienta que envía una copia de sus parámetros a la salida estándar, si el nivel de depuración está activo, y añade esa misma información al archivo de registro junto con la fecha y el PID.

function f_trace() {
    [ $DEBUG -gt 0 ] && echo "$@"
}

function f_LOG() {
    f_trace "$@"   
    echo "`date`:$$:$@" >> $LOG_FILE
}

Una vez que lo anterior existe y es funcional añadimos más funciones que emplean a f_LOG() etiquetando la información según su nivel de registro:

function f_INFO() {
   f_LOG "INFO: $@"
}

function f_WARNING() {
   f_LOG "WARNING: $@"
}

Los valores globales del módulo podrían gestionarse de esta forma:

# Sin depuración 
${DEBUG:=0}

# Archivo de registro 
LOG_FILE=${LOG_FILE:="/tmp/$(basename ${0})-${$}.log"}


Eliminando espacios sobrantes en variables

Para recortar los espacios en blanco de un texto (valor alfanumérico) al principio o al final del mismo se puede emplear una combinación de expresiones encadenadas entre sí:

# eliminando espacios al comienzo del texto 
texto="${texto#"${texto%%[![:space:]]*}"}"

# y después al final 
texto="${texto%"${texto##*[![:space:]]}"}"

Ambas expresiones realizan las siguientes tareas:

  1. ${texto%%[![:space:]]*} extrae el fragmento de texto desde el principio hasta el primer carácter que no sea un espacio en blanco. La siguiente línea del código anterior hace lo mismo pero buscando desde el final hasta que encuentra un carácter que no es un espacio en blanco.
  2. ${texto#"EXPRESION_1"} busca EXPRESION_1 en el comienzo del texto, la extrae, y retorna el contenido.
  3. texto="EXPRESION_2" evalúa EXPRESION_2 y asigna el resultado de nuevo a la variable.

Código de salida en una tubería

Cuando para un trabajo se emplea una batería de programas enlazados entre sí mediante una tubería y algo falla no se puede usar la variable $? para determinar qué sucedió porque hace referencia al último proceso de la cadena. En su lugar Bash rellena una variable tipo lista llamada PIPESTATUS con los códigos de salida de cada uno de los procesos de la tubería en orden de aparición, reservando el índice -1 para almacenar el resultado final.

#!/bin/bash 

# Lanzamos programas 
programa1 | programa2 | programa3 
if [ ${PIPESTATUS:-1} -ne 0 ]; then 
   [ ${PIPESTATUS[0] -ne 0 ] && echo "fallo de programa1"
   [ ${PIPESTATUS[1] -ne 0 ] && echo "fallo de programa2"
   [ ${PIPESTATUS[2] -ne 0 ] && echo "fallo de programa3"
else
   echo "Sin problema :-)"
fi

Atrapando señales del sistema

Para capturar señales recibidas por el proceso y ejecutar alguna acción concreta se emplea la instrucción trap de la siguiente forma:

#!/bin/bash
# traptest.sh

trap "echo Booh!" SIGINT SIGTERM
echo "pid is $$"

while :			# This is the same as "while true".
do
        sleep 60	# This script is not really doing anything.
done

Convirtiendo rutas de archivos

DIR=../probe.d
ABSDIR=$(readlink -m $DIR)

Para convertir una ruta de archivo o un enlace simbólico a una ruta absoluta se puede emplear readlink.

Leyendo archivos línea a línea

#!/bin/bash
while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
done < "$1"

Leyendo archivos con espacios en el nombre

#!/bin/bash
SAVEIFS=$IFS
IFS=$(echo -en "\n\b")
for f in *
do
  echo "$f"
done
IFS=$SAVEIFS

Leyendo contraseñas

#!/bin/bash
# Read Password
echo -n Password: 
read -s password
echo
# Run Command
echo $password

Guardando todas las salidas en un archivo

#!/bin/bash
exec 3>&1 4>&2
trap 'exec 2>&4 1>&3' 0 1 2 3
exec 1>log.out 2>&1

Histórico