Cambie el nombre de los archivos de forma recursiva utilizando una lista de patrones y reemplazos

Cambie el nombre de los archivos de forma recursiva utilizando una lista de patrones y reemplazos

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, sedo mven 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 findsolo directorios de red y bájelos shde un trago. Esto minimiza el número de invocaciones de sh.
  • Configure finden cada uno de estos directorios archivos de red regular, con un nivel de profundidad de 1 únicamente, y aliméntelos shde un trago. Esto minimiza la cantidad de veces renameque se llama a la utilidad.
  • Configure un whilebucle para leer los distintos pattern <-> replacementpares y aplicarlos en todos los regulararchivos.
  • En el proceso de rename-En gMantenemos una nota sobre si un archivo seguía en pie después del renameproceso. 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 siguiente pat/repliteración. OTOH, si el nombre del archivo se cambió correctamente, NO aplicamos la siguiente pat/repliteració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.csvy recorre sus líneas llenando las variables $patterny $replacement. En el segundo paso, ./filesse 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 findmirar solo los directorios en el árbol. Y para cada directorio buscamos todos los regular filesque 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
' {} {} \;

información relacionada