Tengo la siguiente estructura de archivos:
- algún directorio
- Algún archivo.txt
- Otro archivo aquí.log
- Otro archivo más.mp3
- Otro directorio
- Con algún otro archivo.txt
- Archivo en nivel raíz.txt
- Otro archivo en el nivel raíz.ext
Lo que quiero hacer ahora es ejecutar un pequeño script que toma otro archivo como entrada que contiene algún tipo de pares de patrón/reemplazo para cambiar el nombre de estos archivos de forma recursiva según ellos. De modo que cada "otro" (no distingue entre mayúsculas y minúsculas) se reemplaza por "foo" o cada "algunos" por "bar".
Ya intenté muchas cosas iterando sobre archivos y leyendo dicho archivo de entrada, pero nada funcionó como debería y finalmente logré sobrescribir accidentalmente mi script de prueba. Pero había muchos ls
, while
, sed
o mv
en uso.
Las dos cosas que no pude resolver por mí mismo fueron cómo manejar los espacios en blanco en los nombres de archivos y cómo no manejar archivos que ya habían sido renombrados en una coincidencia de patrón anterior.
¿Quizás puedas indicarme la dirección correcta?
Respuesta1
TOP="`pwd -P`" \
find . -type d -exec sh -c '
for d
do
cd "$d" && \
find . ! -name . -prune -type f -exec sh -c '\''
while IFS=\; read -r pat repl
do
rename "s/$pat/$repl/g" "$@"
N=$#
for unmoved
do
if [ -f "$unmoved" ]
then
set X ${1+"$@"} "$unmoved"
shift
fi
done
shift "$N"
case $# in 0 ) break ;; esac
done < patterns.csv
'\'' x \{\} +
cd "$TOP"
done
' x {} +
- Configure
find
solo directorios de red y bájelossh
de un trago. Esto minimiza el número de invocaciones desh
. - Configure
find
en cada uno de estos directorios archivos de redregular
, con un nivel de profundidad de 1 únicamente, y aliméntelossh
de un trago. Esto minimiza la cantidad de vecesrename
que se llama a la utilidad. - Configure un
while
bucle para leer los distintospattern <-> replacement
pares y aplicarlos en todos losregular
archivos. - En el proceso de
rename
-En gMantenemos una nota sobre si un archivo seguía en pie después delrename
proceso. Si encontramos que un archivo todavía existe, eso significa que, por alguna razón, no se le puede cambiar el nombre y, por lo tanto, se intentará en la siguientepat/repl
iteración. OTOH, si el nombre del archivo se cambió correctamente, NO aplicamos la siguientepat/repl
iteración en este archivo eliminándolo de la lista de argumentos de la línea de comando.
Respuesta2
rPairs="/tmp/rename_pairs" \
find . -type f -exec sh -c '
while read -r old new; do
rename "s/$old/$new/i" "$@"
done < "$rPairs"
' x {} +
Suponiendo que no hay caracteres que no sean ASCII en su archivo de pares de cambio de nombre y que este archivo esté ubicado fuera de la ruta de búsqueda.
Respuesta3
Después de la respuesta de Rakesh Sharma, tomé la dirección correcta después de experimentar un poco más y dormir un poco.
Finalmente se me ocurrió el siguiente script:
#!/bin/bash
while IFS=";" read pattern replacement
do
if [[ ! -z $pattern ]]
then
echo "Checking files for pattern '$pattern'."
find ./files -name "*$pattern*" -type f | while read fpath
do
fname=$(basename "$fpath")
dname=$(dirname "$fpath")
echo " Found file '$fname' in directory '$dname'. Renaming to '${fname/$pattern/$replacement}'."
mv -- "$fpath" "$dname/${fname/$pattern/$replacement}"
done
fi
done < patterns.csv
Lee el archivo pattern.csv
y recorre sus líneas llenando las variables $pattern
y $replacement
. En el segundo paso, ./files
se encuentran todos los archivos dentro de un directorio que coinciden con el patrón actual. Esto debe hacerse para evitar intentar cambiar el nombre de los archivos nuevamente cuando coincida un segundo patrón, ya que eso fallaría. Finalmente, solo cambia el nombre del archivo en sí, no de los directorios que lo contienen mediante la sustitución de parámetros del shell.
Lo que no funciona es reemplazar las coincidencias sin distinguir entre mayúsculas y minúsculas, pero puedo vivir con eso.
Respuesta4
El punto importante a tener en cuenta es que recorrer el árbol de directorios es un proceso lento, por lo que se realiza sólo una vez. Lo que hacemos es primero find
mirar solo los directorios en el árbol. Y para cada directorio buscamos todos los regular files
que hay debajo de ellos (aquí no hay recursividad). Luego aplicamos la transformación de cambio de nombre en estos nombres de archivos y al mismo tiempo tomamos nota de si tuvo éxito o no. Si tiene éxito, saldremos del bucle while evitando así que se aplique el siguiente patt/repl en este archivo.
tempd="`mktemp -d`" \
find . -type d -exec sh -c '
cd "$1" && \
for f in ./*
do
[ -f "$f" ] || continue
while IFS=\; read -r patt repl
do
case $f in
./*"$patt"* )
rename -v "s/$patt/$repl/g" "$f" 2>&1 | tee "$tempd/$f"
case $(< "$tempf/$f") in "$f renamed "* ) break ;; esac ;;
esac
done < /tmp/patterns.csv
done
' {} {} \;