Bash: operadores de comparación

Bash tiene un gran soporte de comparadores de todo tipo que nos permiten hacer comparaciones en los bucles y crear condiciones de todo tipo:

Comparación de enteros (números)

  • -eq
    es igual a

    if [ "$a" -eq "$b" ]
  • -ne
    no es igual a / distinto

    if [ "$a" -ne "$b" ]
  • -gt
    es mayor que

    if [ "$a" -gt "$b" ]
  • -ge
    es mayor que o igual a

    if [ "$a" -ge "$b" ]
  • -lt
    es menor que

    if [ "$a" -lt "$b" ]
  • -le
    es menor que o igual a

    if [ "$a" -le "$b" ]
  • <
    es menor que (dentro de doble paréntesis)

    (("$a" < "$b"))
  • <=
    es menor que o igual a (dentro de doble paréntesis)

    (("$a" <= "$b"))
  • >
    es mayor que (dentro de doble paréntesis)

    (("$a" > "$b"))
  • >=
    es mayor que o igual a (dentro de doble paréntesis)

    (("$a" >= "$b"))

Comparación de cadenas

  • =
    es igual a

    if [ "$a" = "$b" ]
  • ==
    es igual a

    if [ "$a" == "$b" ]
  • Nota: Aunque es un sinónimo de = el operador == se comporta diferente cuando se usa dentro de corchetes dobles que simples, por ejemplo:

    [[ $a == z* ]]   # Verdadero si $a empieza con una "z" (expresión regular coincide).
    [[ $a == "z*" ]] # Verdadero si $a es igual a z* (coincide literalmente).
    
    [ $a == z* ]     # Ocurre división de palabras.
    [ "$a" == "z*" ] # Verdadero si $a es igual a z* (coincide literalmente).
  • !=
    no es igual a / Distinto

    if [ "$a" != "$b" ]

    NOTA: este operador usa coincidencia de patrón dentro de doble corchete.

  • <
    es menor que (en orden alfabético ASCII)

    if [[ "$a" < "$b" ]]
    if [ "$a" \< "$b" ]

    Nota: el operador “<” necesita ser escapado dentro de corchetes.

  • >
    es mayor que (en orden alfabético ASCII)

    if [[ "$a" > "$b" ]]
    if [ "$a" \> "$b" ]

    Nota: el operador “>” necesita ser escapado dentro de corchetes.

  • -z
    La cadena está vacía (nulll), tiene longitud cero.

    cadena=''   # Variable de longitud cero (null)
    if [ -z "$String" ]
    then
    echo "\$String está vacía."
    else
    echo "\$String no está vacía."
    fi
  • -n
    cadena no está vacía (contiene algo)
    nota: El operador -n exige que la cadena esté entre comillas entre paréntesis. Aunque el uso son comillas puede funcionar es altamente recomendable usar comillas.

Comparaciones lógicas

  • -a
    Y lógico (and)

    exp1 -a exp2

    devuelve verdadero si ambas exp1 y exp2 son verdaderas.

  • -o
    O lógico (or)

    exp1 -o exp2

    devuelve verdadero si alguna de las expresiones exp1 y exp2 son verdaderas.

Éstos últimos operadores son similares a los operadores de Bash && (and) y || (or) cuando se usan con doble corchete:

[[ condition1 && condition2 ]]

iTerm: poner título a las pestañas

A veces cuando tenemos muchas pestañas abiertas en el iTerm, especialmente en la misma shell (ya sea local o remota)  nos interesa poder distinguirlas. Bien el propio iTerm permite hacerlo, pulsando manzana+i (⌘+i) nos aparece una ventana en la cual podemos cambiar el nombre en el campo “name”.
ventana información iTerm
También podemos añadir un texto a este nombre desde la shell usando un simple echo con una secuencia de escapes:

echo "^[]1;texto^G"

NOTA: NO vale copiar y pegar ya que no funcionará, ^] es control+v y luego esc, ^G es control+v y luego control+g, es muy importante.
Con esto añadimos a la pestaña “texto” a lo que ya estuviera:
Pestaña iTerm con texto añadido
También podemos usar otro tipo de escape que es básicamente lo mismo:

echo -ne "\033]0;texto\007"

Lo interesante de esto es que el texto puede ser una variable o una función de Bash o lo que queramos con lo cual podemos añadir que nos indique el path o directorio actual:

 echo  "^[]1;${PWD/#$HOME/~}^G"

El problema de esto es que no es dinámico y si nos cambiamos de directorio no se cambia a menos que coloquemos esa cadena de escape en la variable PS1 que se encarga de cambiar el prompt:

PS1="\[\033]0;\u@\h: \w\007\]\u@\h:\W>"

Con lo que tanto el prompt como la pestaña se irán actualizando (usuario@host:path>) según vayamos cambiando de directorio.
Yo personalmente como prompt uso el siguiente que es a color y doble línea:

PS1="\[\033[33m\]\\u\[\033[37m\]@\[\033[32m\]\\H\[\033[37m\]:\[\033[36m\]\\w\[\033[37m\]\n\\$ "

Si además queremos que nos actualice la pestaña (solo el path para que se vea mejor y más útil):

PS1="\[\033]0;\w\007\]\[\033[33m\]\\u\[\033[37m\]@\[\033[32m\]\\H\[\033[37m\]:\[\033[36m\]\\w\[\033[37m\]\n\\$ "

Esto lo podemos meter en el fichero .bash_profile de nuestra home:

# Actualiza prompt y pestaña
export PS1="\[\033]0;\w\007\]\[\033[33m\]\\u\[\033[37m\]@\[\033[32m\]\\H\[\033[37m\]:\[\033[36m\]\\w\[\033[37m\]\n\\$ "
# Actualiza solo prompt
export PS1="\[\033[33m\]\\u\[\033[37m\]@\[\033[32m\]\\H\[\033[37m\]:\[\033[36m\]\\w\[\033[37m\]\n\\$ "

NOTA: solo puede estar activo uno de los dos así comenta o borra el que no quieras.

Bash: bucles (for, while, until)

Un bucle es un trozo de código que repite la ejecución de un comando o comandos mientras la condición es verdadera. Bash tiene diferentes formas para realizar bucles (for, while, until).

  • FOR
    Tiene dos formatos, uno tipo lenguaje C y otro totalmente diferente:

    for variable in [lista]
    do
     comando(s)...
    done
    for ((variable=valor_inicial; a <= limite ; incremento))
    do
      comando(s)...
    done
  • WHILE
    while [ condicion ]
    do
     comando(s)...
    done
  • UNTIL
    until [ condicion ]
    do
     comando(s)...
    done

NOTA: si el do va en la misma línea que el for, while o until, antes del do debe haber un ‘;‘. Por ejemplo: “for variable in [lista]; do”.

También como en otros lenguajes podemos tomar el control de los bucles con los siguiente comandos:

  • Continue, provoca que se salte al siguiente valor de la lista o condición ignorando el resto de comandos que haya por debajo.
  • Break, provoca que se salte al siguiente comando justo seguido del bucle aunque este no haya terminado, es una ruptura del bucle.

—- Ejemplos —-

* Bucle FOR simple:

#!/bin/bash

for numeros in 1 2 3 4 5 6 7 8 9
do
  echo $numeros  # cada numero en una linea separada
done

echo

for numeros in "1 2 3 4 5 6 7 8 9"
    # Todos los número en la misma linea.
    # Una lista entre comillas crea una única variable.
do
  echo $numeros
done

Salida:

1
2
3
4
5
6
7
8
9

1 2 3 4 5 6 7 8 9

* Bucle FOR con dos valores en cada elemento de la lista:

#!/bin/bash

# Asociar cada planeta con su distancia del sol.

for planet in "Mercury 36" "Venus 67" "Earth 93"  "Mars 142" "Jupiter 483"
do
  set -- $planet
  #  divide los valores y crea parámetros posicionales ($1, $2 ...)
  #  "--" evita problemas si $planet empieza con guiones o es una cadena vacía.

  echo "$1              $2,000,000 millas del sol"
  #-------2  tabuladores-añade ceros al parámetro $2
done

salida:

Mercury         36,000,000 miles from the sun
Venus           67,000,000 miles from the sun
Earth           93,000,000 miles from the sun
Mars            142,000,000 miles from the sun
Jupiter         483,000,000 miles from the sun

NOTA: Como “set — $planet” sobre escribe los valores de los parámetros $1, $2, etc puede ser necesario guardar los varoles para después recuperarlos. Esto lo podemos hacer con el uso de un array: parametros_originales=(”$@”)

* Bucle FOR al estilo C:

#!/bin/bash

LIMITE=9

for ((a=1; a < = LIMITE ; a++))  # Doble paréntesis y "LIMITE" sin "$".
do
  echo -n "$a " # -n = no añade retorno de carro
done
echo

salida:

1 2 3 4 5 6 7 8 9

* Bucle WHILE simple:

#!/bin/bash

LIMITE=10
a=1

while [ "$a" -le $LIMITE ]
do
  echo -n "$a " # -n = no añade retorno de carro
  let "a+=1"
done

echo

salida:

1 2 3 4 5 6 7 8 9 10

* Bucle UNTIL simple:

#!/bin/bash

LIMITE=10
a=1

until [ "$a" -ge $LIMITE ]
do
  echo -n "$a " # -n = no añade retorno de carro
  let "a+=1"
done

echo

salida:

1 2 3 4 5 6 7 8 9 10

Script conversor de APE/FLAC a MP3

Script para convertir de forma cómoda y automática CDs completos en formatos LOSSLESS (sin perdida) a MP3 para poder reproducirlos en reproductores tipo IPOD.

Descarga la última versión: te2mp3_v0.6.1.sh

La ayuda es la siguiente y puede verse ejecutando el script sin parámetros:

Usage:
./te2mp3_v0.6.1.sh -c CUE_file -f sound_file [-y YEAR] [-g genre] [-o output_directory] [-u] [-r] [-l]
./te2mp3_v0.6.1.sh [-l] [-o output_directory] -b batch_file
./te2mp3_v0.6.1.sh -c Marduk_-_Nightwing.cue -f Marduk_-_Nightwing.flac
./te2mp3_v0.6.1.sh -c Marduk_-_Nightwing.cue -f Marduk_-_Nightwing.flac -y 1998
./te2mp3_v0.6.1.sh -c Marduk_-_Nightwing.cue -f Marduk_-_Nightwing.flac -y 1998 -g 138
./te2mp3_v0.6.1.sh -c Marduk_-_Nightwing.cue -f Marduk_-_Nightwing.flac -y 1998 -o /home/the_evangelist/music -g 138 -u
./te2mp3_v0.6.1.sh -b batch_file
NOTE: -u = change espaces for underscores (Optional)
NOTE: -g = Genre, to see list 'id3v2 -L' (Optional)
NOTE: -r = do not remove temporaly CUE and WAV files (default is remove)
NOTE: -l = create a letter (first letter of band name) directory before band name directory:
NOTE: in Batch mode -l and -o MUST be before -b option)
NOTE: -b = Batch mode has this format (for each file to process):
AUDIO file (including path to)
CUE file (including path to)
YEAR
GENRE (to see list 'id3v2 -L')
UNDERSCORE (1 = yes, any other = no)
---------------------------------------------------------------------
bash       -> Necessary to run this script (Mandatory)
cueprint   -> get info from CUE files (Mandatory)
lame       -> for compressing MP3 files (Mandatory)
shntool    -> for xtracting WAV from compress files (Mandatory)
perl       -> For renaming white spaces to underscores (Mandatory)
mac        -> for decompressing  APE files (Optional)
flac       -> for decompressing FLAC files (Optional)
wavpack    -> for decompressing   WV files (Optional)
hope in future versions will not be necessary
---------------------------------------------------------------------

CHANGELOG

  • 2006-04-22 <-> v0.6.1
    • * Fix Album name problem, change ‘/’ for ‘-’ in Album name
    • * Fix, check if Batch file exists if not show error and exit
  • 2006-04-17 <-> v0.6
    • * change all SED, now using builtin Bash Strings Manipulation
    • now script more portable and one external program less
    • + added option -l to create directory before band name (b/bathory)
    • + added more comments and corrected some grammar ;-)
    • + Now illegal option is print in RED
    • + Now -o OUTPUT can be used in Batch mode
  • 2006-04-16 <-> v0.5
    • * Reorganice script in functions
    • + added batch process (-b ) to convert several files rapidly
    • + added by default genre “Metal” (9), instead of nothing
    • * Fix name problem, change ‘/’ for ‘-’ in title name and mp3 file
    • * Fix problem (ALBUM_DIR) when year (-y) is NOT specified
  • 2006-04-15 <-> v0.4.1
    • * Fix problem with CUE, first line must be FILE “….”
    • now hope will be resolved forever ;-)
    • - cueconvert no more needed
  • 2006-04-14 <-> v0.4
    • * Fix problem when renaming white spaces
    • * Some fixes and improvements
    • + added option -r to remove or not temporal CUE and WAV files
    • + added required commands
    • + added more comments
  • 2006-04-13 <-> v0.3
    • + added capitalize all words
    • NOTE: change perl to sed or something else
    • * Fix CUE bug
    • + added –add-id3v2 option to lame
    • force addition of version 2 tag
  • 2006-04-12 <-> v0.2
    • + added support for parameters (getopts)
    • + added support for genre
    • + added destination path (optional) default current
    • * several bugfixes
  • 2006-04-10 <-> v0.1
    • First version

Find, bucles for y espacios en Bash

Si en la línea del CLI o un script de este tipo:

for LINEA in $(find _directorio_ _que_buscar_ )
do
echo $LINEA
done

nos aparece uno o más espacios no nos saldrá la salida como esperamos

ya que el for interpreta el espacio como un separador con lo cual en vez de tener un listado como este (es un mero ejemplo):

linea1
linea2
linea 3
linea4

obtendremos un listado como este:

linea1
linea2
linea
3
linea4

que está claro no es lo que buscamos :-(.

Para solucionar esto basta con añadir una simple línea delante del For para que “ignore” los espacios y obtengamos el listado como queremos:

export IFS=$'\n'
for LINEA in $(find _directorio_ _que_buscar_ )
do
echo $LINEA
done

Manejo de fechas en BASH

  • Pasar de fecha a número de segundos desde la época
    date --date='1970-01-01 00:00:01' +%s
    1
    date --date='2000-01-01 00:00:01 UTC' +%s
    946684800
  • Pasar de segundos desde la época a fecha
    date -d '1970-01-01 1 sec' +"%Y-%m-%d %T %z"
    1970-01-01 00:00:01 +0100
    date -d '1970-01-01 946684800 sec' +"%Y-%m-%d %T %z"
    2000-01-01 00:00:00 +0000
  • ceros delante de las fechasPor ejemplo si te encuentras con este error:current month is 08 it gives me this error. value too great for base (error token is “08″)

    Revisa la forma de obtener las fechas. (NOTA: lo del guión es válido para cualquier fecha y hora).

    date "+%-m"
    9
    date "+%m"
    09

Soporte de GPG Keyring en APT v0.6 o superior

Instalar el paquete debian-keyring de la siguiente manera:

apt-get install debian-keyring

Si sale este error o similar:

W: GPG error: http://secure-testing.debian.net etch/security-updates
Release: The following signatures couldn't be verified because the public
key is not available: NO_PUBKEY 946AA6E18722E71E

Coger los últimos 8 dígitos de la pubkey (en el ejemplo de arriba: 8722E71E) y ejecutar los siguientes comandos:

$ gpg --keyserver pgp.mit.edu --recv-keys 8722E71E
$ gpg --armor --export 8722E71E | apt-key add -