Bash mover y renombrar analizando la salida de find


3

He anidado directorios de archivos pdf y me gustaría extraerlos a un directorio de nivel superior renombrándolos de la siguiente manera:

Mis archivos son algo como:

./path1/pathA/fileI.pdf
./path1/pathB/fileII.pdf

Quiero lograr:

./path1_pathA_fileI.pdf    
./path1_pathB_fileII.pdf

Sé que puedo hacer una lista de los archivos haciendo

find . -type f -name "*.pdf"

Y puedo imaginar una solución usando

find . -type f -name "*.pdf" | mv -t ...

Pero no sé cómo completar el ... porque no entiendo el análisis y la asignación de variables en bash. ¿Cómo se divide la ruta en el '/' y se forma una nueva ruta y un nombre de archivo como el anterior?

¡Muchas gracias de antemano!

Respuestas:


6

Tratar:

find . -mindepth 2 -type f -name '*.pdf' -exec bash -c 'f=${1#./}; mv "$1" "./${f//\//_}"' None {} \;

Esto es seguro para todos los nombres de archivos, incluso aquellos con nuevas líneas en sus nombres.

Cómo funciona

  • -mindepth 2

    Esto le dice a find que no procese ningún archivo que ya esté en el directorio actual.

  • -type f -name '*.pdf'

    Esto restringe la búsqueda a archivos normales con la pdfextensión.

  • -exec bash -c '...' None {} \;

    Esto ejecuta el comando en la cadena de suministro citado el nombre de archivo que el primer argumento, $1.

    Para nuestros propósitos, la cadena Nonees simplemente un marcador de posición. Se le asigna $0, según las convenciones de bash, el nombre del comando que estamos ejecutando.

  • f=${1#./}; mv "$1" "./${f//\//_}"

    Esto (a) elimina el prefijo ./del nombre del archivo y (b) mueve el archivo a la ubicación deseada con el nuevo nombre.

    ${1#./}es un ejemplo de eliminación de prefijo de bash . Devuelve el strign $1con ./eliminado desde el principio. ${f//\//_}es un ejemplo de sustitución de patrones de bash . Devuelve la cadena $fcon todo /reemplazado por _. Para leer más sobre estas características, consulte la sección man bashtitulada Expansión de parámetros .

Versión más eficiente

La versión anterior invoca bash para cada archivo encontrado. Alternativamente, podemos invocar bash solo una vez para varios archivos encontrados. Para hacer esto, envolvemos nuestro comando en un forbucle:

find . -mindepth 2 -type f -name '*.pdf' -exec bash -c 'for f in "$@"; do f=${f#./}; echo mv "$f" "./${f//\//_}"; done' None {} +

Problema alternativo

Suponga que todos los archivos que queremos están en un directorio de segundo nivel y queremos que los archivos movidos tengan el orden de los nombres de directorio invertidos para que, en lugar de ./path1_pathA_fileI.pdf, terminemos con ./pathA_path1_fileI.pdf. En este caso:

for d1 in */; do d1=${d1%/}; for d2 in "$d1"/*/; do d2=${d2%/}; p="${d2#$d1/}_$d1"; for f in "./$d2"/*.pdf; do echo mv "$f" "./${p}_${f#./$d2/}"; done; done; done

O, para aquellos que prefieren sus comandos distribuidos en varias líneas:

for d1 in */
do
    d1=${d1%/}
    for d2 in "$d1"/*/
    do
         d2=${d2%/}
         p="${d2#$d1/}_$d1"
         for f in "./$d2"/*.pdf
         do
             echo mv "$f" "./${p}_${f#./$d2/}"
         done
    done
done

¡Gracias por el desglose paso a paso! ¿Qué pasaría si quisiera? También quería cambiar el orden de las dos cadenas dir para que una ruta final fuera ./pathA_path1_fileI.pdf En este caso, la sustitución del patrón $ {f // \ // _} no funciona. ¿Como se puede hacer esto?
Canaryyellow

@ Canaryyellow OK. Agregué código para eso.
John1024
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.