viernes, 31 de mayo de 2013

Crea tus propios comandos con funciones de Bash

En programación, una función no es más que una secuencia de instrucciones que realizan una tarea específica, a la cual se le da un nombre que puede invocarse cuando se necesite llevar a cabo dicha tarea en cualquier punto del programa.

Las shells potentes como Bash, que cuentan con sus propios comandos internos y estructuras de control para poder programarlas, también permiten definir funciones. Podemos obtener ayuda al respecto usando el comando interno help, indicado para obtener ayuda básica sobre cada comando interno de Bash: help function. La otra forma de obtener ayuda en el propio sistema sobre los comandos internos de Bash, es consultar la página de manual de los mismos ejecutando: man bash-builtins.

Según puede leerse en la ayuda oficial, podemos crear una función de las siguientes formas (si se escribe un comando por línea no es necesario el punto y coma para separarlos):

function mi_funcion { comando1; comandoN; }
mi_funcion () { comando1; comandoN; }

Ejemplos (hay que tener en cuenta que una tubería de comandos se considera un solo comando, de hay solo el punto y coma final):

function usuarios_totales { grep '/bin/bash$' /etc/passwd | wc -l ; }
usuarios_actuales () { who | cut -d ' ' -f1 | uniq | wc -l ; }

Aunque el uso más conocido de estas funciones de la shell es el de estructurar el código de un shell script de forma que no se repitan secuencias de instrucciones y además facilitar la reutilización de código en otros scripts, la aplicación práctica que me parece más interesante para un usuario avanzado de Linux es poder crear nuestros propios comandos. En el artículo anterior ya vimos como hacerlo con alias, y podríamos usar las funciones de igual modo:

function top_files { ls -lhS | grep ^- | head ; }

o:

topdirs () {
    du -h -d1 | sort -hr | grep / | head
}

Hasta aquí nada nuevo, ya que la funcionalidad que proporcionan es similar a la de los alias y su sintaxis es un poco más compleja. Es cuando se necesita ejecutar distintos comandos (no solo una tubería de ellos) en cierta secuencia, comprobar condiciones o iterar en bucles, cuando los alias se muestran limitados o directamente inútiles (no son más que sustituciones simples) y las funciones empiezan su "función" ;-) También cuando necesitamos pasar algún parámetro que modifique la forma en que se ejecutan los comandos. Por ejemplo, en el caso de las funciones anteriores que hemos adaptado de los alias correspondientes, podríamos controlar el número de archivos o directorios a listar en el ranking:

function top_files {
    local lines
    if [ -z "$1" ]; then
        lines=10
    else
        lines=$1
    fi
    ls -lhS | grep ^- | head -n $lines
}

topdirs () {
    local lines
    if [ -z "$1" ]; then
        lines=10
    else
        lines=$1
    fi
    du -h -d1 | sort -hr | grep / | head -n $lines
}

Como puede verse, el paso de parámetros a la función funciona de la misma forma que el paso de parámetros a un script, por lo que en $1 tendremos el primer parámetro pasado, presumiblemente el número de líneas a filtrar del listado final. El comando local se usa para definir variables locales a la función que no interfieran con las definidas a nivel del script (si la función estuviera dentro de uno, si no el ámbito sería la shell actual). Además hay que mencionar el comando interno return, que se usaría para salir de la función (anticipadamente o no) devolviendo un código de estado, tal y como se hace con exit a nivel de un script.

Las funciones definidas en línea de comandos, al igual que los alias, tienen una vida efímera vinculada a la shell actual. Para que sean útiles deben perdurar en el tiempo, para lo que editaremos el archivo de nuestro usuario .bashrc y añadiremos en él la definición de las mismas. La ventaja sobre un shell script sería la rapidez, ya que se encuentran pre-cargadas en memoria, aunque por eso mismo deberían reservarse para tareas cotidianas cuya secuencia de comandos no sea demasiado grande...