Bash
Versión |
GNU bash, version 3.2.39(1)-release |
Página principal |
http://www.gnu.org/software/bash/ |
Observaciones |
Sumario
- 1 Enlaces y referencias
- 2 Conceptos
- 3 Recetario
- 3.1 Depurando programas
- 3.2 getopt
- 3.3 Funciones de registro de eventos
- 3.4 Eliminando espacios sobrantes en variables
- 3.5 Código de salida en una tubería
- 3.6 Atrapando señales del sistema
- 3.7 Convirtiendo rutas de archivos
- 3.8 Leyendo archivos línea a línea
- 3.9 Leyendo archivos con espacios en el nombre
- 3.10 Leyendo contraseñas
- 3.11 Guardando todas las salidas en un archivo
- 4 Histórico
Enlaces y referencias
- Ref: Bash Reference Manual
- Ref: Advanced Bash Scripting Guide
- Ref: Advanced Bash Scripting by Joshua Malone
- Ref: bash shell: 'exec', 'eval', 'source' - looking for help to understand
- Ref: Bash scripting quirks & safety tips by Julia Evans
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
- Ref: Advanced Bash-Scripting Guide: Functions
- Ref: Returning Values from Bash Functions by Mitch Frazier
- 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
- Ref: Getopt and getopts by A.P. Lawrence
- Ref: The 60 second getopts tutorial
- Ref: Small getopts tutorial on Bash hackers' wiki
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:
${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.${texto#"EXPRESION_1"}
busca EXPRESION_1 en el comienzo del texto, la extrae, y retorna el contenido.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
- Ref: Traps
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