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...

martes, 28 de mayo de 2013

Crea tus propios comandos con alias

Una de las mayores ventajas de cualquier sistema Unix en general, y de Linux en particular, es su capacidad para ser adaptado a las necesidades del usuario. Originalmente desarrollado por y para hackers, entre sus principios de diseño se encuentra la idea de disponer de una generosa colección de herramientas especializadas que puedan combinarse a voluntad para crear nuevas herramientas caseras con las que solucionar los problemas cotidianos surgidos en el sistema informático.

La propia shell (el programa que hace de intermediario entre el usuario y el sistema) cuenta con características que permiten adaptarla y extenderla a las preferencias y necesidades del usuario. En el caso de Bash, la potente shell de GNU presente por defecto en sistemas Linux e incluso en Mac OS X, una característica de este tipo que ofrece al mismo tiempo sencillez y potencia es la definición de alias

Un alias no es más que un sinónimo, un nombre alternativo que el usuario puede definir para un comando ya existente. Podemos examinar los alias existentes ejecutando el comando interno del mismo nombre: alias.

Observando la salida en pantalla de dicho comando se pueden ver algunos alias típicos que suelen definirse por defecto en distribuciones Linux (luego veremos dónde):
  • Por ejemplo, suele haber un sinónimo para ls -l, uno de los comandos más usados, al que se ha bautizado ll. O uno llamado la para el comando ls -a. La idea es darle un nombre corto a ciertos comandos habituales acompañados de distintas opciones de uso, con lo que ahorraremos pulsaciones de teclado y con ello tiempo.
  • Otro tipo de alias muy usado es el que sustituye a un comando externo, p. ej. ls, por su invocación con cierta opción predeterminada, como ls --color=auto. Así cuando se ejecute el comando estándar ls con cualquiera de sus opciones, será como si lo ejecutáramos además con esa útil opción que lo hace colorear su salida de datos en pantalla. Pero sin tener que escribirla cada vez que lo queramos usar. Una variante de este uso es definir alias para ciertos comandos cuyo uso puede tener cierto riesgo, como puede ser el borrado de archivos con rm, por una variante que pregunte al usuario: rm -i. El problema de éste enfoque es que el usuario podría acabar dando por hecho que el comando en cuestión siempre pregunta cuando eso no será cierto en otros sistemas donde no exista dicho alias.
La forma de definir nuevos alias es mediante el comando alias de la forma siguiente:

alias nombre_sinónimo='comando_a_ejecutar'

Por ejemplo, podríamos llamar lla a la combinación de ls con sus opciones -l y -a de la forma:

$ alias lla='ls -la'

Otro uso típico es para acortar simplemente nombres de comando largos que se usan a menudo, por una variante más corta:

$ alias cl='clear'

$ alias p='ping'

O proporcionar un sinónimo conocido para usuarios que vienen de otros sistemas operativos y estén dando sus primeros pasos:

$ alias cls='clear'

$ alias dir='ls'

El último alias podría ser innecesario si existe en el sistema el comando dir. Lo ideal sería comprobar, antes de definir un nuevo alias, si existe algún comando con el nombre elegido para evitar conflictos...

Para eliminar un alias previamente definido, usaremos el comando unalias de la forma:

unalias alias_a_eliminar

Debemos tener en cuenta que los alias que definamos sólo existirán en la shell actual. Si queremos que persistan entre distintas sesiones o incluso reinicios del sistema operativo, tendremos que definirlos, en lugar de en la línea de comandos, en uno de los archivos de configuración de la shell. Para bash, el archivo adecuado sería .bashrc o mejor .bash_aliases en caso de existir.

Hasta aquí el uso básico de los alias, pero donde comienza su verdadera potencia es cuando se usan para dar nombre a tuberías de comandos, es decir a combinaciones de comandos que proporcionan soluciones a medida para problemas concretos. Un par de ejemplos:
  • alias topfiles='ls -lhS | grep ^- | head' # Nuevo comando que lista, ordenados por tamaño y empezando por el mayor, los 10 archivos más grandes del directorio actual.
  • alias topdirs='du -h -d1 | sort -hr | grep / | head' # Nuevo comando que lista, ordenados por tamaño y empezando por el mayor, los 10 directorios que más espacio consumen bajo el directorio actual.
Si los comandos de la tubería no requieren ningún parámetro variable que controle su ejecución, los alias pueden ser una primera alternativa a los shell scripts. La segunda, que introduciré en el siguiente post, son las funciones de la shell.