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:
1 2 3 4 5 6 7 | #!/bin/bash FICHERO="archivo.txt" NOMBRE="${FICHERO%.*}" EXTENSION="${FICHERO##*.}" echo $NOMBRE echo $EXTENSION |
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:
1 | EXTENSION=$([[ "$FICHERO" = *.* ]] && echo "${FICHERO##*.}") |
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:
1 2 3 4 5 6 7 8 9 10 11 | FICHERO="archivo.tar.bz2" NOMBRE="${FICHERO%.*}" EXTENSION="${FICHERO##*.}" echo $EXTENSION while [ -n "$EXTENSION" ]; do FICHERO="${FICHERO%.*}"; EXTENSION=$([[ "$FICHERO" = *.* ]] && echo "${FICHERO##*.}"); echo $EXTENSION; done |
Pero si queremos extraer todas las extensiones juntas del archivo, deberíamos hacer:
1 | EXTENSION=$([[ "$FICHERO" = *.* ]] && echo "${FICHERO#*.}") |
Y si queremos el nombre sin ninguna extensión:
1 | NOMBRE="${FICHERO%%.*}" |
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:
1 2 3 4 5 6 7 8 9 10 11 | function my_filename() { local FILE="$1" [[ "$2" = "1" ]] && echo "${FILE%.*}" || echo "${FILE%%.*}" } function my_extension() { local FILE="$1" [[ "$FILE" = *.* ]] && ( [[ "$2" = "1" ]] && echo "${FILE#*.}" || echo "${FILE##*.}") } |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | function my_filename() { local FILE="$1" FILE="${FILE##*/}" [[ "$2" = "1" ]] && echo "${FILE%.*}" || echo "${FILE%%.*}" } function my_extension() { local FILE="$1" FILE="${FILE##*/}" [[ "$FILE" = *.* ]] && ( [[ "$2" = "1" ]] && echo "${FILE#*.}" || echo "${FILE##*.}") } function my_dirname() { local PATH="$1" [[ "$PATH" = */* ]] && echo "${PATH%/*}" } |
Con las que tendremos un acceso más fácil y amigable para nuestros scripts.
Foto principal: Christopher Adams
Pingback: Cómo extraer ruta, nombre de fichero y extensión en Bash de forma nativa para nuestros scripts | PlanetaLibre /