4. Programación shell

Ahora que sabemos manejarnos con el shell y conocemos unos pocos comandos, vamos a comenzar a hacer pequeños programas que interpretará el shell.

En esta parte necesitaremos un editor de texto plano, como pueden ser: vi, emacs, joe, mcedit, nano, kwrite, gedit, etc. Cualquiera de ellos vale, siempre que guardemos el texto como text/plain.

4.1. Mira mamá, soy un script!

Vamos a crear un fichero, script.sh, con el siguiente contenido:

#!/bin/sh
echo Mira mamá, soy un script!

Intentamos ejecutarlo con ./script.sh y no funciona. Esto es porque la extensión sh no es lo que hace que sea ejecutable. Para que se pueda ejecutar tenemos que darle permisos de ejecución:

$ chmod +x script.sh

Ahora sí es ejecutable.

El programa consta de dos lineas:

4.2. Variables

Una variable es un contenedor. Consta de un identificador que la distingue de otra (su nombre) y de un contenido. La relación entre variable y contenido es de equivalencia.

Por lo general las variables en shell no tienen tipos asociados y se definen de la siguiente forma:

identificador = contenido

    Ejemplos:
    
    # i vale 1
    i=1
    
    # I vale echo
    I=echo
    
    # msg vale Hola mundo!
    msg="Hola mundo!"

Cuidado: si dejamos espacios entre el = y el identificador o el valor el shell creerá que son comandos a ejecutar y no la asignación de una variable.

Para acceder al contenido de una variable empleamos $ delante de su identificador:

$identificador

    Ejemplos:
    
    $ i=1 ; echo $i
    
    $ msg="Mola mundo!" ; echo $msg
    
    $ fu=echo; $fu goo!

Cuando empleamos $identificador el shell busca el valor almacenado en la variable asociada a ese identificador y lo utiliza para reemplazar esa ocurrencia de $identificador.

4.3. Linea de comandos

Cuando se ejecuta nuestro programa en shell hay una serie de variables que siempre estarán disponibles, entre ellas las que nos permiten acceder a los distintos argumentos con los que fue ejecutado nuestro script.

$0
    contiene el nombre nombre de nuestro script

$#
    el número de parámetros con los que se ha invocado al shell

$n
    los parámetros, con n de 1 a 9 (a $#)

$$
    el PID de nuestro proceso

$*
    todos los parámetros menos $0

4.4. La salida de los programas

Cuando se ejecuta un programa, un comando UNIX es un programa, podemos, a parte de redirigir su entrada y su salida, recoger el resultado de su ejecución y su salida.

El resultado es un valor numérico, por lo general cero si todo ha ido bien, y distinto de cero si ha habido alguna clase de error.

La salida del programa es lo que obtendríamos en stdin y stdout.

$?
    resultado del último programa ejecutado

    Ejemplo:
    
    $ ls pirulotropical 2> /dev/null ; echo $?
    
    $ ls > /dev/null ; echo $?
    
$(comando)
    la salida de comando (esto es equivalente al uso de comillas invertidas, pero por
    simplicidad vamos a utilizar esta versión)

    Ejemplo:
    
    $ salida_ls=$(ls) ; echo $salida_ls

exit ENTERO
    termina nuestro programa con el valor de salida ENTERO

Ejercicio 1: realizar un script que dado un directorio, cree un archivo tar comprimido con gzip y con nombre igual a la fecha en formato yyyy-mm-dd seguido del nombre del directorio acabado en .tar.gz. Ejemplo: aplicado sobre tmp obtendríamos -> 2004-04-03tmp.tar.gz.

4.5. Operaciones aritméticas

Para que el shell evalue una operación aritmética y no la tome como argumentos de un comando, por ejemplo:

$ echo 1+1

Si queremos que sustituya la operación por su valor emplearemos:

$((expresión))
    evalua la expresión aritmética y reemplaza el bloque por el resultado
    
    Ejemplo:
    
    $ echo $((1+1))

Algunos operadores aritméticos soportados:

+    suma
*    mutiplicación
-    resta
/    división entera
%    resto de la división entera
( )  agrupar operaciones

Ejercicio 2: realizar un script que dado un número 'n' muestre los diez primeros elementos de su tabla de multiplicar, mostrando el resultado en la forma: i x n = resultado.

4.6. Condicionales

Existe un comando para evaluar condiciones, y que nos permitirá que nuestros programas "tomen decisiones":

test expresion
[ expresion ]

Este comando evalua expresion, y si evalua a cierto, devuelve cero (true), o en otro caso 1 (false). Si no hay expresión, test siempre devuelve falso. Este comportamiento puede ser algo confuso, ya en lógica los valores cierto y falso suelen ser al contrario.

test soporta gran cantidad de operadores, algunos son:

-d fichero
    cierto si fichero existe y es un directorio
-e fichero
    cierto si fichero existe, independientemente del tipo que sea
-f fichero
    cierto si fichero existe y es un fichero normal
-r fichero
    cierto si fichero existe y se puede leer
-s fichero
    cierto si fichero existe y tiene tamaño mayor que cero
-w fichero
    cierto si fichero existe y es se puede escribir sobre él
-x fichero
    cierto si fichero existe y es ejecutable

n1 -eq n2
    cierto si los enteros n1 y n2 son iguales
n1 -ne n2
    cierto si los enteros n1 y n2 no son iguales
n1 -gt n2
    cierto si el enteros n1 es mayor que n2
n1 -ge n2
    cierto si los enteros n1 y n2 son iguales o n1 es mayor
    que n2
n1 -lt n2
    cierto si el enteros n1 es menor que n2
n1 -le n2
    cierto si los enteros n1 y n2 son iguales o n1 es menor
    que n2

s1 = s2
    cierto si las cadenas de texto s1 y s2 son idénticas
s1 != s2
    cierto si las cadenas de texto s1 y s2 no son idénticas
s1 < s2
    cierto si la cadena de texto s1 es menor que s2
s1 > s2
    cierto si la cadena de texto s1 es mayor que s2
-n cadena
    cierto si la longitud de la cadena de texto es distinta de cero

! expresion
    cierto si expresion es falsa (negación)
expresion1 -a expresion2
    cierto si expresion1 y expresion2 son ciertas
expresion1 -o expresion2
    cierto si expresion1 o expresion2 son ciertas
    

Además existen los operadores lógicos && (AND, multiplicación lógica) y || (OR, suma lógica), que se puede aplicar al valor de salida de los programas:

$ true && true ; echo $?
$ true && false ; echo $?
$ false && true ; echo $?
$ false && false ; echo $?

$ true || true ; echo $?
$ true || false ; echo $?
$ false || true ; echo $?
$ false || false ; echo $?

El sistema de evaluación del shell es perezoso y va de izquierda a derecha. Si se encuentra la suma lógica true || ALGO, ALGO no se evaluará porque se asume que cierto o falso o cierto o cierto siempre es cierto (toma ya).

4.7. Bucles

El shell aporta mecanismos para realizar tareas repetitivas mediante el empleo de estructuras que permiten repetir un bloque de comandos.