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:
- =~ 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) ;; *) ;; esacAritmé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 ... donePatró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 regularPatró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]}" doneRedirecciones
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 $aParalelismo
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
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 &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 doneDeclarando 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;lt;&amp;lt;&amp;lt; "Campo1 Campo2" # O bien, usar la redireccion simple, en lugar de un pipe: while read C1 C2; do .... done &amp;lt; ficheroCuriosidades
Mis favoritas:
- Alt + . (último parámetro)
- ${SECONDS}
- ${RANDOM}
- TMOUT
Operaciones con conjuntos en shell