Cómo extraer ruta, nombre de fichero y extensión en Bash de forma nativa para nuestros scripts

Publicado el 20 julio 2016 por Gaspar Fernández Moreno @gaspar_fm


Bash tiene infinidad de opciones, y en los últimos años se ha extendido muchísimo y nos permite hacer cosas muy chulas. Aunque un sistema con tantas opciones como este, es también un poco lioso y difícil de aprender. Por eso en ocasiones viene bien una chuleta para realizar operaciones sencillas que pueden llegar a ser un poco rebuscadas como obtener el nombre de un fichero y su extensión.

Bash, al ser un intérprete de comandos de consola, una de sus principales funciones es trabajar con archivos y cuando queremos utilizar archivos, tenemos que jugar con sus posibles nombres, para ello, deberíamos poder extraer fácilmente la ruta de un archivo, su nombre y separarlo de la extensión. Por ejemplo, si queremos transformar varios archivos de un directorio de jpg a png, o hacer un script que trate de forma los archivos de imagen y de otra los vídeos, o incluso contar cuántos archivos tienen qué extensión...

Para ello, y a modo de autochuleta también, vamos a plantear varios ejemplos en los que se utilizan los modificadores # y % de expansión de parámetros en Bash:

Si ejecutamos esto veremos que primero se mostrará el nombre (archivo) y luego la extensión (txt), aunque, esto deberíamos complicarlo un poco más, y plantearnos preguntas para verificar que funciona en todos los casos, por ejemplo, ¿qué pasaría si el archivo no tiene extensión? En este caso, devolvería el mismo nombre del archivo, y eso puede darnos problemas, para solucionarlo, plantearemos EXTENSION de la siguiente forma:

Ahora bien, ¿qué pasaría si nos pasan un archivo con doble extensión? Como "archivo.tar.bz2″, la variable NOMBRE contendría (archivo.tar) y EXTENSION contendría (bz2). Esto puede ser útil en ciertas ocasiones, por ejemplo, podríamos recorrer todas las posibles extensiones del archivo:

Pero si queremos extraer todas las extensiones juntas del archivo, deberíamos hacer:

Y si queremos el nombre sin ninguna extensión:

Lo que variamos es el número de % y de # que colocamos. En este caso, si utilizamos # estaremos eliminando el prefijo de un patrón, es decir, la parte a la izquierda, y como eliminamos *. queremos decir que eliminamos todo lo que hay delante del punto, ahora si usamos un sólo # estaremos eliminando lo menor posible y si usamos dos, lo mayor posible. De esta forma ${FICHERO#*.} al eliminar poco, nos devuelve todas las extensiones y ${FICHERO#*.}, al encontrar el patrón más grande posible, muestra sólo la última extensión.

De esta forma, nos podemos crear nuestras propias funciones:

Así si hacemos varias llamadas:

$ my_filename archivo
archivo
$ my_filename archivo.tar
archivo
$ my_filename archivo.tar.bz2
archivo
$ my_filename archivo.tar.bz2 1
archivo.tar

$ my_extension archivo

$ my_extension archivo.tar
tar
$ my_extension archivo.tar.bz2
bz2
$ my_extension archivo.tar.bz2 1
tar.bz2

Incluyendo directorios

Si queremos que estas funciones separen también nombres de archivo de directorios tenemos varias opciones. Una de ellas es utilizar basename y dirname, que son dos comandos que muchas veces están disponibles para extraer nombres de archivos y nombres de directorios, así:

$ basename /usr/share/sane/xsane/archivo.tar.bz2
archivo.tar.bz2
$ dirname /usr/share/sane/xsane/archivo.tar.bz2
/usr/share/sane/xsane

Aunque si queremos una solución puramente hecha en Bash, y así evitar hacer llamadas externas y ganar algo de rendimiento podemos utilizar las mismas técnicas anteriores. Es más, primero, vamos a incluir rutas de archivo con las funciones anteriores, a ver qué pasa:

$ my_filename "/usr/share/sane/xsane/archivo.tar.bz2″
/usr/share/sane/xsane/archivo
$ my_extension "/usr/share/sane/xsane/archivo.tar.bz2″
bz2

En principio el nombre incluye la ruta completa, pero la extensión se comporta bien, aunque si el directorio contiene un punto también...

$ my_extension /etc/init.d/lm-sensors
d/lm-sensors

Por tanto, primero vamos a intentar eliminar las rutas, y extraer el nombre de archivo base. Esto lo podemos hacer basándonos en el patrón entre la última barra y el final del nombre, o lo que es lo mismo, eliminando desde el principio del nombre hasta la última barra:

$ echo "${FICHERO##*/}"

y para extraer la ruta solamente, podremos utilizar algo similar a la extensión:

$ $([[ "$FICHERO" = */* ]] & echo "${FICHERO%/*}")

Esto mismo, lo podemos trasladar a las funciones, de la siguiente forma:

Con las que tendremos un acceso más fácil y amigable para nuestros scripts.

Foto principal: Christopher Adams