Bash

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)

Trazas mejoradas con «:»

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:

  • =~ expresiones regulares
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:

# clasica
FILES=`ls`
# contemporanea en linea. Ventajas: se pueden anidar varias
FILES=$(ls)
# contemporanea a array (Asignar el resultado a un array)
FILES=($(ls))

# 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 &quot;${linea}&quot;
done
# fuera del bucle, la variable linea no está accesible
echo ${linea}

# readarray funciona como un alias de mapfile
declare -a lineas
mapfile lineas &amp;amp;lt; 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 &amp;amp;lt;&amp;amp;lt;&amp;amp;lt; &quot;Campo1 Campo2&quot;
# O bien, usar la redireccion simple, en lugar de un pipe:
while read C1 C2; do .... done &amp;amp;lt; 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

Dejar un comentario

Tu dirección de correo electrónico no será publicada.