viernes, 21 de junio de 2013

Funciones en un shell script: trackbin

Hace poco comentaba lo útil que podía resultar definir nuevas funciones en la shell y usarlas como si se tratara de nuevos comandos, nuestros propios comandos. El otro uso de las funciones shell, quizá más clásico, es dentro de scripts, como un recurso para que el programador organice su código. Cuando cierta secuencia de instrucciones (en este caso comandos) se utiliza repetidas veces a lo largo de un programa (en nuestro caso script) lo mejor es declararlo como una función que se invocará allá donde sea necesario, teniendo en cuenta ciertas cuestiones:
  • A una función se la invoca como a un comando o script, por su nombre y adjuntando si son necesarios los parámetros separados por espacios.
  • Dichos parámetros (para ser preciso argumentos, una vez dentro de la función), los recibirá en las variables especiales $1,$2, etc. solapando temporalmente las variables equivalentes donde el script recibe los parámetros pasados por el usuario desde la línea de comandos.
  • Si se necesita alguna variable local a la función, se puede usar la palabra reservada local antes de asignarle un valor por primera vez.
  • La función puede devolver con return un código de estado al igual que el script lo devuelve a la shell con exit. Éste se podrá usar para comprobar su éxito o no a modo de función que devuelve un valor booleano en lenguajes de programación tradicionales.
  • La forma de modificar y/o devolver otros datos más allá de ese valor de retorno, sería modificando variables globales (cualquiera definida en el script que no se haya "tapado" con una local del mismo nombre) o sacando datos a la salida estándar (un caso típico sería una función ayuda_de_uso()), que siempre podrían recogerse en el punto de la invocación dentro de una variable con la sustitución de comandos (mediante comando o $(comando) si no se quieren sacar aun por la salida éstandar.
Para ilustrarlo incluyo más abajo el código de un shell script llamado trackbin que rastrea los nombres de comandos hasta el archivo ejecutable último siguiendo los links intermedios:
#!/bin/bash

if [ -z "$1" ]; then
    echo "Usage: $(basename $0) BINARY_NAME"
    exit 1
fi

bin=$(which $1)

if [ -z "$bin" ]; then
    echo "$(basename $0): error: command '$1' not found"  
    exit 1
fi

function es_enlace {
    ls -l $(which $1) | grep -q ' -> '
    return $?
}

function donde_apunta {
    local destino=$(ls -l $(which $1) | awk '{print $NF}')
    if ! echo $destino | grep -q '/'; then
        destino=$(dirname $1)/$destino
    fi
    echo $destino
}

while es_enlace $bin
do
    bin=$(donde_apunta $bin)
done

echo $bin

exit 0