lunes, 31 de julio de 2017

¿Qué son los arrays?

Los arrays son variables que almacenan más de un valor a la vez. Los arrays se organizan como una tabla. Tomemos como ejemplo una hoja de cálculo. Una hoja de cálculo funciona como un array de dos dimensiones. Tiene tanto filas como columnas, y una celda individual de la hoja de cálculo puede localizarse según su dirección de fila y columna. Un array se comporta de la misma forma. Un array tiene celdas, que se llaman elementos, y cada elemento contiene datos. Un elemento individual de un array es accesible usando una dirección llamada un indice o subscript.

La mayoría de lenguajes de programación soportan arrays multidimensionales. Una hoja de cálculo es un ejemplo de un array multidimensional de dos dimensiones, ancho y alto. Muchos lenguajes de programacion soportan arrays con un número arbitrario de dimensiones, aunque los arrays de dos y tres dimensiones son probablemente los más usados.

Los arrays en bash se limitan a una única dimensión. Podemos pensar que son hojas de cálculo con una única columna. Incluso con esta limitación, hay muchas aplicaciones para ellos. El soporte para arrays apareció por primera vez en la versión 2 de bash. El programa de shell original de Unix, sh, no soportaba arrays de ningún tipo.

viernes, 28 de julio de 2017

Arrays

En el último capítulo, vimos como el shell puede manipular cadenas y números. El tipo de datos que hemos visto hasta ahora se conoce en los círculos informáticos como variables escalares; es decir, variables que contienen un solo valor.

En este capítulo, veremos otro tipo de estructura de datos llamadas arrays (vectores), que contienen múltiples valores. Los arrays son una característica de prácticamente todos los lenguajes de programación. El shell también los soporta, aunque de una forma algo limitada. Incluso así, pueden ser muy útiles para resolver problemas de programación.

jueves, 27 de julio de 2017

Para saber más

miércoles, 26 de julio de 2017

Punto extra

Aunque la funcionalidad básica del script loan-calc es correcta, el script está lejos de estar completo. Como punto extra, prueba a mejorar el script loan-calc con las siguientes características:
  • Verificación completa de los argumentos de la línea de comandos.
  • Una opción de línea de comandos para implementar un modo "interactivo" que pregunte al usuario el capital, el tipo de interés y el plazo del préstamo.
  • Un mejor formateo de la salida.

martes, 25 de julio de 2017

Resumiendo

En este capítulo, hemos aprendido muchas pequeñas cosas que pueden usarse para hacer "trabajo real" en scripts. A medida que nuestra experiencia en scripts aumente, la capacidad de manipular eficientemente cadenas y números nos será de mucho valor. Nuestro script loan-calc demuestra que incluso scripts simples pueden crearse para hacer cosas realmente útiles.

lunes, 24 de julio de 2017

Un script de ejemplo

Como ejemplo de la vida real, construiremos un script que realice un cálculo común, el pago mensual de un préstamo. En el siguiente script, usamos un documento-aquí para pasar un script a bc:

#!/bin/bash

# loan-calc : script to calculate monthly loan payments

PROGNAME=$(basename $0)

usage () {
    cat <<- EOF
    Usage: $PROGNAME PRINCIPAL INTEREST MONTHS

    Where:

    PRINCIPAL is the amount of the loan.
    INTEREST is the APR as a number (7% = 0.07).
    MONTHS is the length of the loan's term.

    EOF
}

if (($# != 3)); then
    usage
    exit 1
fi

principal=$1
interest=$2
months=$3

bc <<- EOF
    scale = 10
    i = $interest / 12
    p = $principal
    n = $months
    a = p * ((i * ((1 + i) ^ n)) / (((1 + i) ^ n) - 1))
    print a, "\n"
EOF

Cuando lo ejecutamos, el resultado aparece así:

[me@linuxbox ~]$ loan-calc 135000 0.0775 180
1270.7222490000

Este ejemplo calcula el pago mensual de un préstamo de 135.000 $ a un interés anual del 7.75% en 180 meses (15 años). Fíjate la precisión de la respuesta. Viene determinada por el valor dado a la variable especial scale en el script bc. La man page de bc proporciona una descripción completa del lenguaje de scripting de bc. Aunque su notación matemática es ligeramente diferente de la del shell (bc se parece más a C), la mayor parte nos resultará familiar, basándonos en lo aprendido hasta ahora.

viernes, 21 de julio de 2017

Usando bc

Si guardamos el script bc anterior como foo.bc, podemos ejecutarlo de la siguiente manera:

[me@linuxbox ~]$ bc foo.bc
bc 1.06.94
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
4

Si miramos con atención, podemos ver el resultado abajo del todo, tras el mensaje de copyright. Este mensaje puede suprimirse con la opción -q (quiet).

bc también puede usarse interactivamente:

[me@linuxbox ~]$ bc -q
2 + 2
4
quit

Cuando usamos bc interactivamente, simplemente tecleamos los cálculos que queremos realizar, y los resultados se muestran inmediatamente. El comando quit de bc finaliza la sesión interactiva.

También es posible pasar un script a bc a través de la entrada estándar:

[me@linuxbox ~]$ bc < foo.bc
4

La capacidad de tomar entrada estándar significa que podemos usar documentos-aquí, cadenas-aquí y tuberías para pasar scripts. Aquí tenemos un ejemplo de cadena:

[me@linuxbox ~]$ bc <<< "2+2"
4

miércoles, 19 de julio de 2017

bc - Un lenguaje de cálculo de precisión arbitraria

Hemos visto cómo el shell puede manejar todos los tipos de aritmética con enteros, pero ¿qué pasa si necesitamos realizar matemáticas más complejas o incluso usar números de coma flotante? La respuesta es, no podemos. Al menos no directamente con el shell. Para hacerlo, necesitamos usar un programa externo. Hay varios enfoques que podemos tomar. Incluir programas Perl o AWK es una solución posible, pero desafortunadamente, están fuera del objetivo de este libro.

Otro acercamiento es usar un programa de cálculo especializado. Uno de estos programas que encontramos en la mayoría de los sistemas Linux se llama bc.

El programa bc lee un archivo escrito en su propio lenguaje tipo C y lo ejecuta. Un script bc puede ser un archivo por separado o puede leerse de la entrada estándar. El lenguaje bc tiene bastantes características incluyendo variables, bucles y funciones definidas por el programador. No veremos bc completamente aquí, sólo lo probaremos. bc está bien documentado por su man page.

Empecemos con un ejemplo simple. Escribiremos un script bc para sumar 2 más 2:

/* A very simple bc script */

2 + 2

La primera línea del script es un comentario. bc usa la misma sintáxis para comentarios que el lenguaje de programación C. Los comentarios, que pueden abarcar varias líneas, comienzan con /* y finalizan con /*.

lunes, 17 de julio de 2017

Lógica

Como descubrimos en el Capítulo 27, el comando compuesto (( )) soporta una variedad de operadores de comparación. Hay unos pocos más que pueden usarse para evaluar la lógica. Aquí tenemos la lista completa:

Tabla 34-6: Operadores de comparación
Operador Descripción
<= Menor o igual que
>= Mayor o igual que
< Menor que
> Mayor que
== Igual a
!= No igual a
&& AND lógico
|| OR lógico
expr1?expr2:expr3 Operador de comparación (ternario). Si expr1 se evalúa como distinta de cero (aritmética verdadera) entonces expr2, si no expr3.

Cuando se usan para operaciones lógicas, las expresiones siguen las reglas de la lógica aritmética; es decir, las expresiones que se evalúan como cero se consideran falsas, mientras que las expresiones distintas de cero se consideran verdaderas. El comando compuesto (( )) mapea los resultados dentro de los códigos de salida normales de shell:

[me@linuxbox ~]$ if ((1)); then echo "true"; else echo "false"; fi
true
[me@linuxbox ~]$ if ((0)); then echo "true"; else echo "false"; fi
false

El operador lógico más extraño es el operador ternario. Este operador (que se modeló después del que hay en el lenguaje de programación C) realiza un test lógico autónomo. Puede usarse como un tipo de sentencia if/then/else. Actúa en tres expresiones aritméticas (las cadenas no funcionarán), y si la primera expresión es verdadera (o distinta de cero) se ejecuta la segunda expresión. Si no, se ejecuta la tercera expresión. Podemos probarlo en la línea de comandos:

[me@linuxbox ~]$ a=0
[me@linuxbox ~]$ ((a<1?++a:--a))
[me@linuxbox ~]$ echo $a
1
[me@linuxbox ~]$ ((a<1?++a:--a))
[me@linuxbox ~]$ echo $a
0

Aquí vemos al operador ternario en acción. Este ejemplo implementa un interruptor. Cada vez que el operador se ejecuta, el valor de la variable a cambia de cero a uno o viceversa.

Por favor, fíjate que realizar asignación dentro de expresiones no es sencillo.

Cuando lo intentamos, bash devuelve un error:

[me@linuxbox ~]$ a=0
[me@linuxbox ~]$ ((a<1?a+=1:a-=1))
bash: ((: a<1?a+=1:a-=1: attempted assignment to non-variable (error token is "-=1")

Este problema puede evitarse incluyendo la expresión de asignación entre paréntesis:

[me@linuxbox ~]$ ((a<1?(a+=1):(a-=1)))

A continuación, vemos un ejemplo más completo del uso de operadores aritméticos en un script que produce una tabla simple de números:

#!/bin/bash

# arith-loop: script to demonstrate arithmetic operators

finished=0
a=0
printf "a\ta**2\ta**3\n"
printf "=\t====\t====\n"

until ((finished)); do
    b=$((a**2))
    c=$((a**3))
    printf "%d\t%d\t%d\n" $a $b $c
    ((a<10?++a:(finished=1)))
done

En este script, implementamos un bucle until basado en el valor de la variable finished. Inicialmente, la variable está establecida en cero (aritmética falsa) y continuamos el bucle hasta que se convierta en distinta de cero. Dentro del bucle, calculamos el cuadrado y el cubo de la variable contador a. Al final del bucle, el valor de la variable contador se evalúa. Si es menor de 10 (el número máximo de iteraciones), se incrementa en uno, si no, se le da a la variable finished el valor de uno, haciendo a finished aritméticamente verdadera, y terminando de esta forma el bucle. Ejecutar el script da este resultado:

[me@linuxbox ~]$ arith-loop
a   a**2   a**3
=   ====   ====
0   0      0
1   1      1
2   4      8
3   9      27
4   16     64
5   25     125
6   36     216
7   49     343
8   64     512
9   81     729
10  100    1000

viernes, 14 de julio de 2017

Operaciones con bits

Hay una clase de operadores que manipulan números de un modo inusual. Estos operadores trabajan a nivel de bits. Se usan para ciertos tipos de tareas de bajo nivel, a menudo implican configuración o lectura de marcas-bit (bit-flags):

Tabla 34-5: Operadores de bits
Operador Descripción
~ Negación bit a bit. Niega todos los bit de un número.
<< Desplazamiento de bits a la izquierda. Desplaza todos los bits en un número hacia la izquierda.
>> Desplazamiento de bits a la derecha. Desplaza todos los bits en un número hacia la derecha.
& AND bit a bit. Realiza una operación AND en todos los bits en dos números.
| OR bit a bit. Realiza una operación OR en todos los bits en dos números.
^ XOR bit a bit. Realiza una operación OR exclusiva en todos los bits en dos números.

Fíjate que también hay los correspondientes operadores de asignación (por ejemplo, <<==) para todos excepto para la negación bit a bit.

Aquí lo comprobaremos produciendo una lista de potencias de 2, usando el operador de desplazamiento de bits a la izquierda:

[me@linuxbox ~]$ for ((i=0;i<8;++i)); do echo $((1<<i)); done
1
2
4
8
16
32
64
128

miércoles, 12 de julio de 2017

Asignación

A pesar de que su utilidad puede no ser inmediatamente aparente, las expresiones aritméticas pueden realizar asignación. Hemos realizado asignación muchas veces, pero en un contexto diferente. Cada vez que damos un valor a una variable, estamos realizando asignación. También podemos hacerlo dentro de expresiones aritméticas:

[me@linuxbox ~]$ foo=
[me@linuxbox ~]$ echo $foo

[me@linuxbox ~]$ if (( foo = 5 ));then echo "It is true."; fi
It is true.
[me@linuxbox ~]$ echo $foo
5

En el ejemplo anterior, primero asignamos un valor vacío a la variable foo y verificamos que está realmente vacía. A continuación, realizamos un if con el comando compuesto (( foo = 5 )). Este proceso hace dos cosas interesantes: 1) asigna el valor 5 a la variable foo, y 2) se evalúa como verdadera porque se le asignó a foo un valor distinto de cero.

Nota: Es importante recordar el significado exacto del = en la expresión anterior. Un = individual realiza asignación. foo = 5 dice "haz a foo igual a 5", mientras == evalúa equivalencia. foo == 5 dice "¿es foo igual a 5? Esto puede ser muy confuso porque el comando test acepta un = individual para equivalencia de cadenas. Esta es otra razón más para usar los comandos compuestos más modernos [[ ]] y (( )) en lugar de test.

Además del =, el shell también ofrece notaciones que realizan algunas asignaciones muy útiles:

Tabla 34-4: Operadores de asignación
Notación Descripción
parámetro = valor Asignación simple. Asigna valor a parámetro.
parámetro += valor Adición. Equivale a parámetro = parámetro + valor.
parámetro -= valor Sustracción. Equivale a parámetro = parámetro - valor.
parámetro *= valor Multiplicación. Equivale a parámetro = parámetro * valor.
parámetro /= valor División entera. Equivale a parámetro = parámetro / valor.
parámetro %= valor Módulo. Equivale a parámetro = parámetro % valor.
parámetro ++ Variable post-incremental. Equivale a parámetro = parámetro +1 (de todas formas, lo veremos continuación).
parámetro -- Variable post-decremental. Equivale a parámetro = parámetro - 1.
++parámetro Variable pre-incremental. Equivale a parámetro = parámetro +1.
--parámetro Variable pre-decremental. Equivale a parámetro = parámetro -1.

Estos operadores de asignación proporcionan una abreviatura para muchas de las tareas aritméticas comunes. De especial interés son los operadores incrementales (++) y decrementales (--), que aumentan o disminuyen el valor de sus parámetros en uno. Este estilo de notación se toma del lenguaje de programación C y ha sido incorporado a otros lenguajes de programación, incluido bash.

Los operadores pueden aparecer delante o detrás de un parámetro. Aunque ambos aumentan o disminuyen el parámetro en uno, las dos localizaciones tienen una diferencia sutil. Si se coloca delante del parámetro , el parámetro aumenta (o disminuye) antes de que se devuelva el parámetro. Si se coloca después, la operación se realiza después de que el parámetro se devuelva. Esto es un poco extraño, pero está hecho a propósito. Aquí tenemos una demostración:

[me@linuxbox ~]$ foo=1
[me@linuxbox ~]$ echo $((foo++))
1
[me@linuxbox ~]$ echo $foo
2

Si asignamos el valor de uno a la variable foo y luego la incrementamos con el operador ++ situado tras el nombre del parámetro, foo es devuelto con el valor de uno. Sin embargo, si comprobamos el valor de la variable una segunda vez, vemos el valor incrementado. Si colocamos el operador ++ delante del parámetro, tenemos el comportamiento esperado:

[me@linuxbox ~]$ foo=1
[me@linuxbox ~]$ echo $((++foo))
2
[me@linuxbox ~]$ echo $foo
2

Para la mayoría de las aplicaciones de shell, colocar un prefijo al operador será lo más útil.

Los operadores ++ y -- se usan a menudo junto con los bucles. Haremos algunas mejoras en nuestro módulo de script ajustándolo un poco:

#!/bin/bash

# modulo2 : demonstrate the modulo operator

for ((i = 0; i <= 20; ++i )); do
    if (((i % 5) == 0 )); then
        printf "<%d> " $i
    else
        printf "%d " $i
    fi
done
printf "\n"

martes, 11 de julio de 2017

Aritmética simple

Los operadores aritméticos ordinarios se listan en la siguiente tabla:

Tabla 34-3: Operadores aritméticos
Operador Descripción
+ Adición
- Sustracción
* Multiplicación
/ División entera
** Exponenciación
% Módulo (resto)

La mayoría son autoexplicativos, pero la división entera y el módulo requieren una explicación.

Como la aritmética del shell sólo opera con enteros, los resultados de la división son siempre números enteros:

[me@linuxbox ~]$ echo $(( 5 / 2 ))
2

Esto hace que la determinación del resto de una división sea más importante:

[me@linuxbox ~]$ echo $(( 5 % 2 ))
1

Usando los operadores división y módulo, podemos determinar que 5 dividido entre 2 da como resultado 2, con un resto de 1.

Calcular el resto es útil en bucles. Permite que una operación se realice en intervalos especificados durante la ejecución del bucle. En el ejemplo siguiente, mostramos una línea de números, destacando cada múltiplo de 5:

#!/bin/bash

# modulo : demonstrate the modulo operator

for ((i = 0; i <= 20; i = i + 1)); do
    remainder=$((i % 5))
    if (( remainder == 0 )); then
        printf "<%d> " $i
    else
        printf "%d " $i
    fi
done
printf "\n"

Cuando se ejecuta, el resultado es el siguiente:

[me@linuxbox ~]$ modulo
<0> 1 2 3 4 <5> 6 7 8 9 <10> 11 12 13 14 <15> 16 17 18 19 <20>

lunes, 10 de julio de 2017

Operadores unarios

Hay dos operadores unarios, el + y el -, que se usan para indicar si un número es positivo o negativo, respectivamente. Por ejemplo, -5.

viernes, 7 de julio de 2017

Bases numéricas

Cuando estábamos en el Capítulo 9, echamos un vistazo a los octales (base 8) y hexadecimales (base 16). En las expresiones aritméticas, el shell soporta constantes enteras en cualquier base.

Tabla 34-2: Especificando diferentes bases numéricas
Notación Descripción
número Por defecto, los números sin notación se tratan como enteros decimales (base 10).
0número En expresiones aritméticas, los números que comienzan con un cero se consideran octales.
0xnúmero Notación hexadecimal
base#número número en base base

Algunos ejemplos:

[me@linuxbox ~]$ echo $((0xff))
255
[me@linuxbox ~]$ echo $((2#11111111))
255

En los ejemplos anteriores, mostramos el valor de un número hexadecimal ff (el mayor número de dos dígitos) y el mayor número binario (base 2) de ocho dígitos.

miércoles, 5 de julio de 2017

Evaluación aritmética y expansión

Hemos visto la expansión aritmética en el Capítulo 7. Se usa para realizar varias operaciones aritméticas con enteros. Su forma básica es:

$((expresión))

donde expresión es una expresión aritmética válida.

Esto está relacionado con el comando compuesto (( )) usado para evaluación aritmética (pruebas de verdad) que vimos en el Capítulo 27.

En capítulos anteriores, vimos algunos de los tipos comunes de expresiones y operadores. Aquí, veremos una lista más completa.

lunes, 3 de julio de 2017

Conversión de mayúsculas y minúsculas

Las versiones recientes de bash tiene soporte para conversión mayúsculas/minúsculas de cadenas. bash tiene cuatro expansiones de parámetros y dos opciones para el comando declare para soportarlo.

¿Y para qué sirve la conversión mayúsculas-minúsculas? Además del obvio valor estético, tiene un importante papel en programación. Consideremos el caso de una búsqueda en una base de datos. Imagina que un usuario ha introducido una cadena en un campo de entrada de datos que queremos buscar en una base de datos. Es posible que el usuario introduzca todo el valor en mayúsculas o todo en minúsculas o una combinación de ambas. Ciertamente, no queremos abarrotar nuestra base de datos con cada combinación posible de letras mayúsculas y minúsculas. ¿qué hacemos?

Una aproximación común a este problema es normalizar la entrada del usuario. Es decir, convertirla en una forma estandarizada antes de intentar la búsqueda en la base de datos. Podemos hacer esto convirtiendo todos los caracteres de la entrada del usuario a mayúsculas o minúsculas y asegurarnos de que las entradas en la base de datos están normalizadas de la misma forma.

El comando declare puede usarse para normalizar cadenas a mayúsculas o minúsculas. Usando declare, podemos forzar que una variable siempre contenga el formato deseado sin importar lo que tenga asignado:

#!/bin/bash

# ul-declare: demonstrate case conversion via declare

declare -u upper
declare -l lower

if [[ $1 ]]; then
    upper="$1"
    lower="$1"
    echo $upper
    echo $lower
fi

En el script anterior, usamos declare para crear dos variables, upper y lower. Asignamos el valor del primer argumento de la línea de comandos (parámetro posicional 1) a cada variable y luego las mostramos en pantalla:

[me@linuxbox ~]$ ul-declare aBc
ABC
abc

Como podemos ver, el argumento de línea de comandos ("aBc") ha sido normalizado.

Hay cuatro expansiones de parámetros que realizan la conversión mayúsculas/minúsculas:

Tabla 34-1: Expansión de parámetros para conversión mayúsculas/minúsculas
Formato Resultado
${parámetro,,} Expande el valor de parámetro a todo en minúsculas.
${parámetro,} Expande el valor de parámetro cambiando sólo el primer carácter a minúsculas.
${parámetro^^} Expande el valor de parámetro a todo en mayúsculas.
${parámetro^} Expande el valor de parámetro cambiando sólo el primer carácter a mayúsculas.

Aquí tenemos un script que demuestra estas expansiones:

#!/bin/bash

# ul-param - demonstrate case conversion via parameter expansion

if [[ $1 ]]; then
    echo ${1,,}
    echo ${1,}
    echo ${1^^}
    echo ${1^}
fi

Aquí está el script en acción:

[me@linuxbox ~]$ ul-param aBc
abc
aBc
ABC
ABc

De nuevo, procesamos el primer argumento de la línea de comandos y obtenemos las cuatro variantes soportadas por las expansiones de parámetros. Aunque este script usa el primer parámetro posicional, parámetro puede ser una cadena, una variable o una expresión.