BashGuide
Bite size bash
Breve introducción a las “shells”, comparativa con zsh
“Pure bash bible” – colección de fragmentos de código en bash que reemplazan llamadas a programas externos.
Shellcheck, un “linter” para bash (integrable en emacs via FlyMake o FlyCheck)
Aspectos básicos
“Depurar” código bash
Lo más directo es habilitar las trazas mediante el parámetro -x de bash (equivalente a la opción xtrace). Se puede refinar definiendo algunas variables:
# xtrace -> imprimir trazas
set -o xtrace
# version abreviada de la linea anterior
set -x
# noglob -> impedir el procesamiento de metacaracteres en nombres de fichero ("globbing")
set -o noglob
set -f
# verbose -> imprimir las líneas de código según se van leyendo
set -o verbose
set -v
# nounset -> abortar al intentar usar una variable no definida
set -o nounset
set -u
# cambiando la variable PS4 se puede personalizar las trazas. Por ejemplo, añadir el número de línea
export PS4='+(${BASH_SOURCE}:${LINENO}): ${FUNCNAME[0]:+${FUNCNAME[0]}(): }'
Variables
Valores por defecto: con “:-” se asigna el valor a la variable. Con “:=” simplemente se retorna el valor por defecto (cuando la variable no tiene valor), esto es, no se modifica la variable.
login="pepe"
export login="pepe"
echo ${login}
echo ${login:=login no definido}
echo ${login:-invitado}
Cadenas
linea="tres tristes"
linea+=" tigres"
echo ${linea}
# Subcadenas
${linea:posicion:longitud}
${linea: -posicion:longitud} # ojo al espacio antes de "-", es importante
# Eliminar subcadenas
f="/usr/lib/libkewl.so"
# borrar por el principio
${linea#*/} # elimina la subcadena de menor longitud, comenzando por el principio
${linea##*/} # elimina la subcadena de mayor longitud
# borrar por el final
${linea%/*} # elimina la subcadena de menor longitud, comenzando por el final
${linea%/*} # elimina la subcadena de mayor longitud, comenzando por el final
# Sustitucion
${linea/patron/sustitucion} # primera aparicion
${linea//patron/sustitucion} # todas apariciones
# Longitud cadena
${#string}
expr length $string
# tests con cadenas
# cadena vacia / no vacia
[ -z "${s1}" ] # cadena vacia
[ -n "${s2}" ] # cadena no vacia
# Pertenencia
[[ "${linea}" == *tristes* ] ]
# Split / join
# usando sustitución
readonly DIR_SEP="/"
array=(${f//${DIR_SEP}/ })
second_dir="${array[1]}"
# usando IFS
hora="HH:MM:SS"
IFS=":" read -ra campos <<< "${hora}"
# Para agrupar palabras en una linea (por ejemplo, con nombres que incluyen espacios), cambiar el separador IFS a \n (escapandolo con $):
IFS=$'\n'
Control de flujo
Condiciones: dentro de [ ] para seguir el estándar POSIX (“clásico”). Para usar las bondades de la extensión del estándar, meter la condición dentro de [[ ]].
También se puede comparar directamente el código de retorno (sin corchetes)
Operadores “clásicos”:
- ==, != comparacion cadenas, -z longitud cero, -n longitud no cero
- -f fichero, -d directorio, -r lectura, -w escritura, -x ejecutable
- -eq -gt -lt -ne -ge -le: comparacion numerica
Operadores de la extensión del estándar:
if [ cond ]; then
b1
elif [[ "${linea}" == *tristes* ] ]; then
b2
elif grep -qF "string" file; then
echo 'file contains "string"'
else
b3
fi
for x in SECUENCIA; do
echo $x
done
case PATTERN in
p1)
;;
...
pm)
;;
*)
;;
esac
Aritmética
Tradicionalmente se hacían con expr o bc, pero hoy en día suele ser más práctico usar los “dobles paréntesis”:
(( a ++ ))
(( x = a>3?1:0 ))
# si queremos hacer la asignación fuera de los dobles paréntesis,
# hay que añadir un dolar:
Z=$(( x + 4 ))
# número aleatorio
x=$RANDOM
# módulo
let "x %= 100"
Límites numéricos de bash (ver también los límites de awk)
Vectores (arrays)
Empiezan en 0
#operaciones basicas
a[3]=X
echo ${a[3]}
# longitud
${#arrayname[@]}
# se puede crear un array facilmente con ():
a=(1 2 3 4)
Se puede asignar tal cual una “lista” separada por espacios:
declare -a nombres
nombres=(paco pablo pato)
Esto se puede combinar con la sintaxis “salida de un comando”, $(), para llegar al combo ($( )):
nombres=($(ls))
Patrón “lista de directorios”
for d in $(ls -d */); do
...
done
Patrón “glob” (operar recursivamente partiendo de un directorio)
find d -exec cmd1 {} \; -exec cmd2 {} \;
Patrón “fichero de configuración”
En bash, es preferible escribir el fichero de configuración en bash y “cargarlo” con source (o su abreviatura, “.”), a usar otra sintaxis y tener que parsearlo como texto…
Patrón “procesamiento de patrones”
Con el operador =~ es bastante simple:
# para no tener que escapar la expresion regular, lo mejor es meterla en una variable
my_regex="([[:alpha:][:blank:]]*)- ([[:digit:]]*) - (.*)$"
if [[ "$CDTRACK" =~ ${my_regex} ]]; then
echo Track ${BASH_REMATCH[2]} is ${BASH_REMATCH[3]}
mv "$CDTRACK" "Track${BASH_REMATCH[2]}"
fi
# BASH_REMATCH contiene los emparejamientos de la ultima expresion regular
Patrón “diccionario”
Los diccionarios se conocen en bash como “arrays asociativos”
# -A -> array asociativo
declare -A rutas
rutas[pepe]="/users/g1/pepe"
clave="pepe"
echo ${rutas[${clave}]}
rutas=([marcos]="/users/g1/marcos")
rutas+=([lucas]="/users/g3/lucas")
# asignación múltiple
rutas([pedro]="/users/g2/pedro" [antonio]="/users/g1/antonio")
# clave con espacios
rutas["jose luis"]="/users/tic/jl"
echo ${rutas[antonio]}
ruta=${rutas[antonio]}
# todos los valores
echo ${rutas[@]}
# todas las claves
echo ${!rutas[@]}
# patrón "imprimir diccionario"
for usuario in "${!rutas[@]}"; do
echo "${usuario} ${rutas[$user]}"
done
Redirecciones
Redirección básica: salida (“>”), entrada (“<")
Añadir: ">>”
“Triple redireccion”: pasa una cadena por la entrada estándar
echo 1234X-D | sudo joe -c “passwd”
sudo joe -c “passwd <<< 1234X-D"
Usar procesos en vez de ficheros (Process substitution)
Con <() se conecta la entrada estándar de un comando con la salida estándar de los comandos dentro de los paréntesis.
Sustitución de comando por su salida:
[code]
# clasica
FILES=`ls`
# contemporanea en linea. Ventajas: se pueden anidar varias
FILES=$(ls)
# contemporanea a array (Asignar el resultado a un array)
FILES=($(ls))
[/code]
# Añadir una cabecera a una lista para que column formatee la cabecera y la lista
column -t <(echo "Permisos i user group count fecha hora nombre") <(ls -l | sed 1d)
# Opciones: >() o <() (sin espacio entre el < o el > y los parentesis)
comm <(ls -l) <(ls -al)
“Punteros”
p=x
x=2
# Imprime x
echo $p
eval p=\$$p
# Imprime 2
echo $a
Paralelismo
El clasico “&” y los coprocesos (coproc)
Expresiones estilo C
# Solo operacion (no se usa el valor de retorno)
(( a = 20 )) # mantener todos los espacios
(( a ++ ))
(( x = a>3?1:0 ))
echo $x # imprime 1
# Para valor de retorno hay que añadir $
Z=$(( x + 4 ))
Patrón “procesar parámetros”
* Procesar parametros del script con getopts
# El primer parametro es la lista de switches que se aceptan, un “:” despues de una letra indica que ese
# switch acepta un parametro, por ejemplo -b hola
while getopts “:ab:cd” Option
do
case $Option in
b) echo “b” parameter is $OPTARG
…
esac
done
* Secuencias de números
for i in `seq 1 20`…
for i in {1..20}…
# Con ceros delante
for i in `seq -f %03.0f 1 10`…
for i in `printf ‘%03d ‘ {0..123}`…
Proceso de texto
Leer líneas
Bash FAQ 001
read / mapfile / readarray
cat fichero | while read -r linea; do
echo "${linea}"
done
# fuera del bucle, la variable linea no está accesible
echo ${linea}
# readarray funciona como un alias de mapfile
declare -a lineas
mapfile lineas < fichero
echo ${lineas[1]}
En ambos casos, ojo con las subshells
Patrón “leer columnas”
# con read
# ojo: como las tuberías crean subshells,
# no se puede acceder desde fuera del bucle a las variables leidas...
cat fichero | while read C1 C2 C3; do
echo $C2
done
Declarando una variable como array, es “facil” sacar los campos de una linea en bash:
declare -a TEST
TEST=(a b c d)
echo ${TEST[0]}
# a
TEST=(1 b c d)
echo ${TEST[0]}
# 1
# Con triple redireccion:
read C1 C2 <<< "Campo1 Campo2"
# O bien, usar la redireccion simple, en lugar de un pipe:
while read C1 C2; do .... done < fichero
Curiosidades
Hidden features of bash
Mis favoritas:
- Alt + . (último parámetro)
- ${SECONDS}
- ${RANDOM}
- TMOUT
Operaciones con conjuntos en shell
Bash one-liners
Página principal GNU Bash
Bash hackers Wiki
Bash FAQ (en “Greycat’s Wiki”)
Documentación sobre Bash en StackOverflow Documentation