¡Una joya, oiga!
Bien después de haber perdido un par de horas, tengo que darme ánimos. ;-)
En serio, me ha parecido un buen ejercicio. Utiliza muchísimas cosas de bash (evaluación previa de comandos con eval, expansión de parámetros, vectores, funciones, recursividad, historial de directorios usando 'pushd' en vez de 'cd', etc...). Para el que esté aprendiendo bash o sólo tenga unos conocimientos básicos, me parece bastante provechoso que lo vaya leyendo e intentando entenderlo con la página del manual de bash.
El script es recursivo, sigue los directorios y también los enlaces simbólicos que apuntan a otros directorios. Y, supuestamente, sin entrar en recursividad infinita: si un directorio ya lo visitó, rechazará volver a visitarlo.
Funcionamiento:
Admite la lista de ficheros bien en la propia línea de comandos o bien a través de la entrada estandar. Por ejemplo, este es el directorio de pruebas:
.:
total 4
drwxr-xr-x 4 josem usuarios 4096 2004-02-06 21:07 cercaycerca
./cercaycerca:
total 8
drwxr-xr-x 2 josem usuarios 4096 2004-02-06 19:44 cerca cerca
drwxr-xr-x 3 josem usuarios 4096 2004-02-06 21:07 mucerca
-rw-r--r-- 1 josem usuarios 0 2004-02-06 09:17 nomuycerca
./cercaycerca/cerca cerca:
total 0
./cercaycerca/mucerca:
total 4
drwxr-xr-x 2 josem usuarios 4096 2004-02-06 09:18 aquicerca
lrwxrwxrwx 1 josem usuarios 6 2004-02-06 19:38 cerca-inicio -> ../../
./cercaycerca/mucerca/aquicerca:
total 0
Hay de todo un poco: unos cuantos niveles de directorios y además un directorio con un espacio en medio del nombre y un enlace simbólico que regresa a un nivel superior, de modo que si uno no se cuida de hacer comprobaciones, entra en un bucle infinito.
La forma de usarlo sería, por ejemplo
$ rename.sh 's/cerca/lejos/g' *
Es decir, el primer parámetro una expresión regular de sed. Esta del ejemplo sustituye todas las apariciones de la cadena "cerca" por la cadena "lejos". El segundo parámetro es la lista de ficheros a los que se quiere aplicar la sustitución. En este caso "*", es decir, todos:
$ rename.sh 's/cerca/lejos/g' *
Renombrando ficheros aplicando el guión de sed 's/cerca/lejos/g'...
Cambiando al directorio /home/josem/pruebas/musikal/cercaycerca...
Cambiando al directorio /home/josem/pruebas/musikal/cercaycerca/cerca cerca...
Retrocediendo a /home/josem/pruebas/musikal/cercaycerca...
/home/josem/pruebas/musikal/cercaycerca/cerca cerc ---> /home/josem/pruebas/musikal/cercaycerca/lejos lejos
Cambiando al directorio /home/josem/pruebas/musikal/cercaycerca/mucerca...
Cambiando al directorio /home/josem/pruebas/musikal/cercaycerca/mucerca/aquicerca...
Retrocediendo a /home/josem/pruebas/musikal/cercaycerca/mucerca...
/home/josem/pruebas/musikal/cercaycerca/mucerca/aq ---> /home/josem/pruebas/musikal/cercaycerca/mucerca/aquilejos
cerca-inicio ---> lejos-inicio
Retrocediendo a /home/josem/pruebas/musikal/cercaycerca...
/home/josem/pruebas/musikal/cercaycerca/mucerca ---> /home/josem/pruebas/musikal/cercaycerca/mulejos
nomuycerca ---> nomuylejos
Retrocediendo a /home/josem/pruebas/musikal...
/home/josem/pruebas/musikal/cercaycerca ---> /home/josem/pruebas/musikal/lejosylejos
Acaba sin entrar en un bucle infinito y deja el árbol de pruebas así:
.:
total 4
drwxr-xr-x 4 josem usuarios 4096 2004-02-06 21:17 lejosylejos
./lejosylejos:
total 8
drwxr-xr-x 2 josem usuarios 4096 2004-02-06 19:44 lejos lejos
drwxr-xr-x 3 josem usuarios 4096 2004-02-06 21:17 mulejos
-rw-r--r-- 1 josem usuarios 0 2004-02-06 09:17 nomuylejos
./lejosylejos/lejos lejos:
total 0
./lejosylejos/mulejos:
total 4
drwxr-xr-x 2 josem usuarios 4096 2004-02-06 09:18 aquilejos
lrwxrwxrwx 1 josem usuarios 6 2004-02-06 19:38 lejos-inicio -> ../../
./lejosylejos/mulejos/aquilejos:
total 0
Si no se quiere que el script actúe de modo recursivo, basta con anteponer a la lista de ficheros un puntro (.):
$ rename.sh 's/lejos/cerca/g' . *
Renombrando ficheros aplicando el guión de sed 's/lejos/cerca/g'...
/home/josem/pruebas/musikal/lejosylejos ---> /home/josem/pruebas/musikal/cercaycerca
También se puede usar la entrada estándar para dar la lista de ficheros:
$ rename.sh 's/lejos/cerca/g'
Introduzca los ficheros a continuación
Escriba tal y como lo haría en la línea de comandos
(comodines, escape de espacios "\ ", etc...)
Pulse Ctrl+D para acabar la entrada
*
../*
Renombrando ficheros aplicando el guión de sed 's/lejos/cerca/g'...
Cambiando al directorio /home/josem/pruebas/musikal/cercaycerca...
Cambiando al directorio /home/josem/pruebas/musikal/cercaycerca/lejos lejos...
Retrocediendo a /home/josem/pruebas/musikal/cercaycerca...
/home/josem/pruebas/musikal/cercaycerca/lejos lejo ---> /home/josem/pruebas/musikal/cercaycerca/cerca cerca
Cambiando al directorio /home/josem/pruebas/musikal/cercaycerca/mulejos...
Cambiando al directorio /home/josem/pruebas/musikal/cercaycerca/mulejos/aquilejos...
Retrocediendo a /home/josem/pruebas/musikal/cercaycerca/mulejos...
/home/josem/pruebas/musikal/cercaycerca/mulejos/aq ---> /home/josem/pruebas/musikal/cercaycerca/mulejos/aquicerca
lejos-inicio ---> cerca-inicio
Retrocediendo a /home/josem/pruebas/musikal/cercaycerca...
/home/josem/pruebas/musikal/cercaycerca/mulejos ---> /home/josem/pruebas/musikal/cercaycerca/mucerca
nomuylejos ---> nomuycerca
Retrocediendo a /home/josem/pruebas/musikal...
Cambiando al directorio /home/josem/pruebas/caquita...
Retrocediendo a /home/josem/pruebas/musikal...
Y aquí va el código:
#!/bin/sh
# rename.sh: Renombra ficheros a partir de una expresión regular de sed
# Sintaxis:
# rename.sh 'expresion-regular' [.] [ficheros]
# 'expresion-regular' = según la sintaxis de sed
# . = implica que no se quiere recursividad
# ficheros = directorios y ficheros a lo que se aplica.
# Se no se indica ninguno, se pedirá su introducción
# por la entrada estándar.
# Ejemplos:
# rename.sh 's/\.JPG$/.jpg/' . *
# Cambia todas las extesiones JPG a jpg. "." implica que no
# se desea recursividad y "*" que se aplica el cambio a todos los
# ficheros del directorio actual
# rename.sh 's/\.JPG$/.jpg/' *
# Como en el caso anterior, pero con recursividad
# rename.sh 's/\.JPG$/.jpg/' * ../*paco*
# Cambia todas las extensiones JPG a jpg recursivamente a los todos
# ficheros del directorio actual y a los ficheros del directorio
# padre que contengan en su nombre la cadena padre.
# rename.sh 's/\.JPG$/jpg/'
# Cambia todas las extensiones JPG por jpg a los ficheros que se
# introduzcan por teclado a continuación
# rename.sh 's/\.JPG$/jpg/' < ficheros.txt
# Cambia todas las extensiones JPG por jpg a los ficheros que se
# encuentren listados en ficheros.txt
function renombra() {
nombrebase=${1##*/}
path=${1%$nombrebase}
destino=${path}`echo $nombrebase | sed "$regexp"`
if [ ! -e "$destino" ]; then
mv "$1" "$destino" && \
printf " %-50.50s ---> %s\n" "$1" "$destino"
fi
}
function recorre() {
local origen # En bash las variables son por defecto ¡globales!
for origen in "$@"; do
[ -d "$origen" ] && [ ! -L "$origen" ] && origen=`readlink -f "$origen"`
if [ "$recursividad" = "SI" ]; then
if [ -L "$origen" ]; then
renombra "$origen" # Renombramos el enlace en sí.
origen=`readlink -f "$origen"` # Calculamos el destino
fi
if [ -d "$origen" ]; then
# Comprobamos si ya se visitó el directorio de destino
for visitado in "${DIRSTACK[@]}"; do
# DIRSTACK almacena $HOME como ~
[ -z "${visitado%%\~*}" ] && visitado="$HOME${visitado#\~}"
[ "$origen" = "$visitado" ] && break
done
if [ "$origen" != "$visitado" ] && pushd "$origen" > /dev/null ; then
echo -e "\e[0;31mCambiando al directorio ${origen}...\e[m"
recorre *
# No borramos el directorio de la pila, sino que
# lo metemos al final, así no desaparece de
# DIRSTACK y podremos saber más adelante si
# anteriormente entramos en él.
pushd +1 > /dev/null
echo -e "\e[0;32mRetrocediendo a $PWD...\e[m"
fi
fi
fi
[ -e "$origen" ] && renombra "$origen"
done
}
# Análisis de los parámetros
if [ ${#} -eq 0 ]; then # No hay parámetros
echo "Error: ha de indicar al menos la expresión regular" >&2
echo "Sintaxis: $0 'regexp' [.] [ficheros]" >&2
exit 1
else
regexp="$1"
if [ "$2" = "." ]; then
recursividad="NO"
preficheros=2
else
recursividad="SI"
preficheros=1
fi
shift $preficheros # Elimina de $@ los parámetros que no son ficheros
if [ ${#} -ne 0 ]; then
ficheros='"$@"'
else # No hay ficheros: entrada estándar
echo "Introduzca los ficheros a continuación"
echo "Escriba tal y como lo haría en la línea de comandos"
echo "(comodines, escape de espacios "\ ", etc...)"
echo "Pulse Ctrl+D para acabar la entrada"
eval entrada=(`cat`) # Escribe las entradas en un vector
ficheros='"${entrada[@]}"'
fi
fi
# Fin del análisis
# Deshace el posible enlace simbólico en el path
pushd `readlink -f "$PWD"` > /dev/null
echo "Renombrando ficheros aplicando el guión de sed '$regexp'..."
echo
eval recorre $ficheros
popd > /dev/null # Regresa al directorio original