Banda Ancha EU

Comunidad de usuarios
de fibra, móvil y ADSL

Ayuda script unix renombrar ficheros dentro de carpeta segun un TXT

Serakon

Para este script ni se por donde empezar al tener nulo nivel de programación, aunque tengo unos cuantos scripts hechos. Pero este no se si podre. Uso Debian 8.

Tendré una carpeta principal con mas carpetas dentro y un fichero dentro de cada una que puede tener cualquier extensión. Y lo que quiero es que ese fichero conservando la extensión pase a tener el nombre que indique el TXT. (y si se añade un filtro para que no se pueda nombrar un fichero con caracteres incompatibles con WINDOWS seria muy bueno)

Consiste en tener un TXT donde guardare nombre-carpeta;nombre (usando ";" como separador) puede tener mas de 15.000 lineas Ejemplo de contenido :

aaaaa88888;Litros (mohamed, el cerdo) eee.rrr.aaaaaaa

bbbb88888;Karismatico(mohamed, el puerco) eee.rrr.aaaaaaa

cccccsssssssss0;Vis a Vis (sj dj, sss,aaaa,) eeeee.lol

Y en la carpeta principal por ejemplo 2 carpetas llamadas asi y con estos ficheros dentro:

bbbb88888

>hola.mp4

cccccsssssssss0
>quistes.jpg

Y la tarea es que renombrara esos ficheros dando de resultado:

bbbb88888

>Karismatico(mohamed, el puerco) eee.rrr.aaaaaaa.mp4

cccccsssssssss0
>Vis a Vis (sj dj, sss,aaaa,) eeeee.lol.jpg

Así a primeras se me ocurre que lo que tendría que hacer es entrar en la carpeta principal y hay como hago en el resto con un FOR ya listo por así decirlo todo lo de la carpeta donde entre... y a partir de ahí lo que no se es como haría para preguntar al TXT que es lo que le corresponde a ese nombre de carpeta en esa linea.

mceds
1

Para navegar línea a línea por el TXT tienes varios métodos:

1. Con un cat TXT | while read LINEA; do ... done . Esto no suele gustar mucho a los "puristas" porque abre un proceso adicional (y puede dar problemas con las variables); pero es muy sencillo: cada línea se va almacenando en la variable $LINEA y, al llegar al final, el READ deja de "alimentarla" y el bucle concluye por sí solo.

2. Con AWK o con SED (y seguro que hay más opciones) también puedes extraer una línea concreta de un archivo de texto. En tal caso, como sugieres, sí que necesitarías un bucle FOR.

A partir de ahí puedes A) preguntar si existe el directorio B) de existir el directorio, renombrar el archivo al que indica la línea.

Para "sacar" el dato adecuado de cada línea (nombre de directorio; nombre de archivo) puedes usar CUT o también AWK. Tengo comprobado que CUT es algo más rápido; pero la ventaja de AWK es que puedes juntar dos pasos en uno (extraer una línea concreta y extraer un dato concreto de esa línea).

Si la velocidad es determinante para ti (y con archivos de 15000 líneas intuyo que sí) y necesitas elegir el mejor método, hay una herramienta muy útil para medir el tiempo que tarda un proceso en realizarse: TIME.

AsmGuy
2

Hola,

Me llama la atención que pongas:

y si se añade un filtro para que no se pueda nombrar un fichero con caracteres incompatibles con WINDOWS seria muy bueno

Si lo tienes accesible con permisos de lectura/escritura desde máquinas Windows, quizás te sea más fácil hacerlo desde allí.

Por ejemplo (mete esto dentro de un fichero con extensión .ps1 y lo ejecutas desde Powershell):

# Guarda tu fichero de entrada en formato UTF8 por si tiene acentos
# Si no sabes en que formato esta, abrelo con Notepad y haz "Guardar como" selecciona formato UTF8

foreach ($linea in Import-Csv FICHERO.TXT -header "Carpeta","Nombre" -delimiter "`t" -encoding utf8)
{
   foreach ($fichero in Get-ChildItem $linea.Carpeta | where { -not $_.PsIsContainer })
   {
      $NuevoNombre = $fichero.DirectoryName + "\" + $linea.Nombre + $fichero.Extension

      # De aqui hasta el write-output te lo puedes saltar si estas totalmente seguro que no hay
      # mas de un fichero con la misma extension en cada carpeta

      if (Test-Path $NuevoNombre)
      {
         $secuencia = 1
         do
         {
            $NuevoNombre = $fichero.DirectoryName + "\" + $linea.Nombre + "_" + $secuencia + $fichero.Extension
            $secuencia++
         } while (Test-Path $NuevoNombre)
      }
      write-output "Renombrando $($fichero.FullName) -> $NuevoNombre"
      Rename-Item $fichero $NuevoNombre
   }
}
🗨️ 1
AsmGuy
1

Perdona, por costumbre he puesto

-delimiter "`t"

pero has de poner

-delimiter ";"

Disculpa por la errata.

Serakon

Gracias a ambos, el de linux mirare a ver como se puede hacer ya conociendo que comandos posibles usar y ver que tal.

Y el de windows la verdad que nunca he hecho ni ejecutado ningún script en windows ni sabia como se hace. Pero me parece bastante interesante probar y ver que tal.

Aunque no tengo ni idea siquiera de como se ponen la ruta de los ficheros para decirle que TXT o que carpeta es.

Pongamos que tengo las carpetas para cambiar en "H:\vOn" y el txt en "E:\Archivoss\listado.txt"

🗨️ 6
AsmGuy
1

Pongamos que tengo las carpetas para cambiar en "H:\vOn" y el txt en "E:\Archivoss\listado.txt"

Primeramente, has de poner la ruta correcta del fichero en la primera línea de código:

foreach ($linea in Import-Csv "E:\Archivoss\listado.txt" -header "Carpeta","Nombre" -delimiter ";" -encoding utf8)

Y dentro del listado pones:

Carpeta1;Nombre1
Carpeta2;Nombre2
...
CarpetaN;NombreN

Y a la hora de ejecutar el programa (supongamos que lo has guardado dentro de E:\Archivos\RenombrarEnMasa.ps1) haces:

cd H:\vOn
E:\Archivos\RenombrarEnMasa.ps1

Con eso ejecutarás el programa, que te irá imprimiendo por pantalla lo que va haciendo.

Una última cosa: en PowerShell, casi todos los comandos aceptan un -WhatIf, que intentan ejecutar el comando pero sin ejecutarlo realmente. Si tienes dudas, ponle un -WhatIf al único comando del programa que cambia algo, que es la línea del Rename-Item.
Te quedaría así:

Rename-Item $fichero $NuevoNombre -WhatIf

Verás las acciones 2 veces: una por el comando Write-Output que he puesto para que se vea que el programa avanza, y otra por el WhatIf del Rename-Item, que te va informando de lo que haría, pero sin hacerlo.

Cuando quieras hacerlo en serio, le quitas el -WhatIf y lo ejecutas.

🗨️ 5
Serakon

Eres un crack hoy probé el script y bien, había un pequeño fallo en la ultima linea y no renombraba, lo corregi sin problemas y he hecho alguna prueba y bien, solo un detalle de que en caso de que ya tenga el nombre que se le va a poner lo renombra añade un "_1" al final pero es algo de poca importancia.

Lo que no se si la salida se podría evitar que mostrara los errores no es vital pero al tener listado 20.000 y 20 carpetas muchas que no tienen su carpeta van lanzando error en pantalla lógicamente.

El script quedo así funcionando:

# Guarda tu fichero de entrada en formato UTF8 por si tiene acentos
# Si no sabes en que formato esta, abrelo con Notepad y haz "Guardar como" selecciona formato UTF8

foreach ($linea in Import-Csv "E:\Portables-V\Scripts\listado.txt" -header "Carpeta","Nombre" -delimiter ";" -encoding utf8)
{
   foreach ($fichero in Get-ChildItem $linea.Carpeta | where { -not $_.PsIsContainer })
   {
      $NuevoNombre = $fichero.DirectoryName + "\" + $linea.Nombre + $fichero.Extension

      # De aqui hasta el write-output te lo puedes saltar si estas totalmente seguro que no hay
      # mas de un fichero con la misma extension en cada carpeta

      if (Test-Path $NuevoNombre)
      {
         $secuencia = 1
         do
         {
            $NuevoNombre = $fichero.DirectoryName + "\" + $linea.Nombre + "_" + $secuencia + $fichero.Extension
            $secuencia++
         } while (Test-Path $NuevoNombre)
      }
      write-output "Renombrando $($fichero.FullName) -> $NuevoNombre"
      Rename-Item $fichero.FullName $NuevoNombre
   }
}

Cambie solo

Rename-Item $fichero $NuevoNombre

por

      Rename-Item $fichero.FullName $NuevoNombre

Muchas Gracias :) y Feliz Navidad.

🗨️ 4
AsmGuy
1

Hola,

Lo de poner "_1" detrás del nombre si ya existe es a posta. El código que hace esto comienza en "# De aqui hasta el write-output te lo puedes saltar......". En realidad, si hay varios ficheros iguales te va a poner "_2", "_3", etc.

Para eliminar los errores que salen por carpetas inexistentes, cambia:

foreach ($fichero in Get-ChildItem $linea.Carpeta | where { -not $_.PsIsContainer })

por:

foreach ($fichero in Get-ChildItem $linea.Carpeta -ErrorAction SilentlyContinue | where { -not $_.PsIsContainer })

O si quieres ser más breve, se puede acortar a:

foreach ($fichero in Get-ChildItem $linea.Carpeta -ea 0 | where { -not $_.PsIsContainer })

Perdona por lo del error de .FullName.

Felices Fiestas!

🗨️ 3
Serakon
Serakon
🗨️ 1
Serakon