Mejor manera de cambiar el nombre de los archivos basados ​​en patrones múltiples

muchos archivos que descargo tienen basura / spam en sus nombres de archivo, por ejemplo

[ www.crap.com ] file.name.ext

www.crap.com - file.name.ext

He encontrado dos maneras de tratar con ellos, pero ambos parecen bastante torpes:

con la expansión de parámetros:

 if [[ ${base_name} != ${base_name//\[+([^\]])\]} ]] then mv -v "${dir_name}/${base_name}" "${dir_name}/${base_name//\[+([^\]])\]}" && base_name="${base_name//\[+([^\]])\]}" fi if [[ ${base_name} != ${base_name//www.*.com - /} ]] then mv -v "${dir_name}/${base_name}" "${dir_name}/${base_name//www.*.com - /}" && base_name="${base_name//www.*.com - /}" fi # more of these type of statements; one for each type of frequently-encountered pattern 

y luego con echo / sed:

 tmp=`echo "${base_name}" | sed -e 's/\[[^][]*\]//g' | sed -e 's/\s-\s//g'` mv "${base_name}" "{tmp}" 

Siento que la expansión de parámetros es la peor de las dos, pero me gusta porque puedo mantener la misma variable asignada al archivo para su posterior procesamiento después del cambio de nombre (el código anterior se usa en una secuencia de comandos que se llama para cada archivo después de que se complete la descarga del archivo).

Así que, de todos modos, esperaba que hubiera una manera mejor / más limpia de hacer lo anterior que alguien más bien informado que yo podría mostrarme, preferiblemente de una manera que me permitiera reasignar fácilmente la variable anterior / original al archivo nuevo / renombrado.

Gracias

Dos respuestas: usar perl cambiar el nombre o usar pure bash

Como hay personas a las que no les gusta Perl, escribí mi versión bash only

Cambiar el nombre de los archivos utilizando el comando de rename .

Introducción

Sí, este es un trabajo típico para el comando de rename de rename que fue diseñado precisamente para:

 man rename | sed -ne '/example/,/^[^ ]/p' For example, to rename all files matching "*.bak" to strip the extension, you might say rename 's/\.bak$//' *.bak To translate uppercase names to lower, you'd use rename 'y/AZ/az/' * 

Muestras más orientadas

Simplemente descarte todos los espacios y corchetes :

 rename 's/[ \[\]]*//g;' *.ext 

Cambie el nombre de todos los .jpg numerando desde 1 :

 rename 's/^.*$/sprintf "IMG_%05d.JPG",++$./e' *.jpg 

Manifestación:

 touch {a..e}.jpg ls -ltr total 0 -rw-r--r-- 1 user user 0 sep 6 16:35 e.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 d.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 c.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 b.jpg -rw-r--r-- 1 user user 0 sep 6 16:35 a.jpg rename 's/^.*$/sprintf "IMG_%05d.JPG",++$./e' *.jpg ls -ltr total 0 -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00005.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00004.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00003.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00002.JPG -rw-r--r-- 1 user user 0 sep 6 16:35 IMG_00001.JPG 

Sintaxis completa para hacer coincidir la pregunta SO, de manera segura

Existe una forma segura y sólida de utilizar la utilidad de rename :

Como esta es una herramienta común de Perl , debemos usar la syntax de Perl:

 rename 'my $o=$_; s/[ \[\]]+/-/g; s/-+/-/g; s/^-//g; s/-\(\..*\|\)$/$1/g; s/(.*[^\d])(|-(\d+))(\.[a-z0-9]{2,6})$/ my $i=$3; $i=0 unless $i; sprintf("%s-%d%s", $1, $i+1, $4) /eg while $o ne $_ && -f $_; ' * 

Regla de prueba:

 touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' ls -1 [ www.crap.com ] file.name.ext www.crap.com - file.name.ext rename 'my $o=$_; ... ... ...' * ls -1 www.crap.com-file.name-1.ext www.crap.com-file.name.ext touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' ls -1 www.crap.com-file.name-1.ext [ www.crap.com ] file.name.ext www.crap.com - file.name.ext www.crap.com-file.name.ext rename 'my $o=$_; ... ... ...' * ls -1 www.crap.com-file.name-1.ext www.crap.com-file.name-2.ext www.crap.com-file.name-3.ext www.crap.com-file.name.ext 

… y así…

… y es seguro mientras no uses -f flag para rename comando: el archivo no se sobreescribirá y obtendrás un mensaje de error si algo sale mal.

Renombrar archivos usando bash y los llamados bashisms :

Prefiero hacer esto usando la utilidad dedicada, pero esto podría hacerse usando pure bash (aka sin ningún tenedor)

No hay uso de ningún otro binario aparte de bash (no sed , awk , tr u otro):

 #!/bin/bash for file;do newname=${file//[ \]\[]/.} while [ "$newname" != "${newname#.}" ] ;do newname=${newname#.} done while [ "$newname" != "${newname//[.-][.-]/.}" ] ;do newname=${newname//[.-][.-]/-};done if [ "$file" != "$newname" ] ;then if [ -f $newname ] ;then ext=${newname##*.} basename=${newname%.$ext} partname=${basename%%-[0-9]} count=${basename#${partname}-} [ "$partname" = "$count" ] && count=0 while printf -v newname "%s-%d.%s" $partname $[++count] $ext && [ -f "$newname" ] ;do :;done fi mv "$file" $newname fi done 

Para ejecutar con archivos como argumento, para muestra:

 /path/to/my/script.sh \[* 
  • Reemplazando espacios y corchetes por puntos
  • Reemplazando secuencias de .- , -. , -- o .. por solo uno - .
  • Prueba si el nombre del archivo no es diferente, no hay nada que hacer.
  • Prueba si existe un archivo con newname
  • dividir nombre de archivo, contador y extensión, para hacer nombre nuevo indexado
  • loop si existe un archivo con newname
  • Finalmente renombra el archivo.

Aproveche el siguiente patrón clásico:

  job_select /path/to/directory| job_strategy | job_process 

donde job_select es responsable de seleccionar los objetos de su trabajo, job_strategy prepara un plan de procesamiento para estos objetos y job_process finalmente ejecuta el plan.

Esto supone que los nombres de archivo no contienen una barra vertical | ni un personaje de nueva línea.

La función job_select

  # job_select PATH # Produce the list of files to process job_select() { find "$1" -name 'www.*.com - *' -o -name '[*] - *' } 

El comando find puede examinar todas las propiedades del archivo mantenido por el sistema de archivos, como la hora de creación, la hora de acceso y la hora de modificación. También es posible controlar cómo se explora el sistema de archivos diciendo que no se desciende en los sistemas de archivos montados, cuántos niveles de recursiones se permiten. Es común agregar tubos al comando find para realizar selecciones más complicadas basadas en el nombre del archivo.

Evite la trampa común de incluir los contenidos de directorios ocultos en el resultado de la función job_select . Por ejemplo, los directorios CVS , .svn , .svk y .git son utilizados por las herramientas de gestión de control de origen correspondientes y casi siempre es incorrecto incluir sus contenidos en la salida de la función job_select . Al procesar estos archivos inadvertidamente por lotes, uno puede hacer que la copia de trabajo afectada resulte inutilizable.

La función job_strategy

 # job_strategy # Prepare a plan for renaming files job_strategy() { sed -e ' h s@/www\..*\.com - *@/@ s@/\[^]]* - *@/@ x G s/\n/|/ ' } 

Este comando lee la salida de job_select y hace un plan para nuestro trabajo de cambio de nombre. El plan está representado por líneas de texto que tienen dos campos separados por el carácter | , el primer campo es el nombre anterior del archivo y el segundo es el nuevo archivo computado del archivo, se ve como

 [ www.crap.com ] file.name.1.ext|file.name.1.ext www.crap.com - file.name.2.ext|file.name.2.ext 

El progtwig particular usado para producir el plan es esencialmente irrelevante, pero es común usar sed como en el ejemplo; awk o perl para esto. Déjenos caminar a través de la sed scripts utilizada aquí:

 h Replace the contents of the hold space with the contents of the pattern space. … Edit the contents of the pattern space. x Swap the contents of the pattern and hold spaces. G Append a newline character followed by the contents of the hold space to the pattern space. s/\n/|/ Replace the newline character in the pattern space by a vertical bar. 

Puede ser más fácil usar varios filtros para preparar el plan. Otro caso común es el uso del comando stat para agregar tiempos de creación a los nombres de archivo.

La función job_process

 # job_process # Rename files according to a plan job_process() { local oldname local newname while IFS='|' read oldname newname; do mv "$oldname" "$newname" done } 

El separador de campo de entrada IFS se ajusta para permitir que la función lea la salida de job_strategy . Declarar oldname y newname como local es útil en progtwigs grandes, pero se puede omitir en scripts muy simples. La función job_process se puede ajustar para evitar sobrescribir los archivos existentes e informar sobre los elementos problemáticos.

Acerca de las estructuras de datos en los progtwigs shell Tenga en cuenta el uso de las tuberías para transferir datos de una etapa a la otra: los aprendices a menudo se basan en variables para representar dicha información, pero resulta ser una elección torpe. En cambio, es preferible representar los datos como archivos tabulares o como flujos de datos tabulares que se mueven de un proceso a otro, de esta forma, los datos se pueden procesar fácilmente mediante potentes herramientas como sed , awk , join , paste y sort , solo para citar los más comunes.

Si está utilizando Ubunntu / Debian os use el comando rename para cambiar el nombre de varios archivos a la vez.

Si desea usar algo que no dependa de perl, puede usar el siguiente código (llamémoslo sanitizeNames.sh ). Solo muestra algunos casos, pero es fácilmente extensible mediante la sustitución de cadenas, tr (y sed también).

  #!/bin/bash ls $1 |while read f; do newfname=$(echo "$f" \ |tr -d '\[ ' \ # Removing opened square bracket |tr ' \]' '-' \ # Translating closing square bracket to dash |tr -s '-' \ # Squeezing multiple dashes |tr -s '.' \ # Squeezing multiple dots ) newfname=${newfname//-./.} if [ -f "$newfname" ]; then # Some string magic... extension=${newfname##*\.} basename=${newfname%\.*} basename=${basename%\-[1-9]*} lastNum=$[ $(ls $basename*|wc -l) ] mv "$f" "$basename-$lastNum.$extension" else mv "$f" "$newfname" fi done 

Y úsala:

  $ touch '[ www.crap.com ] file.name.ext' 'www.crap.com - file.name.ext' '[ www.crap.com ] - file.name.ext' '[www.crap.com ].file.anothername.ext2' '[www.crap.com ].file.name.ext' $ ls -1 *crap* [ www.crap.com ] - file.name.ext [ www.crap.com ] file.name.ext [www.crap.com ].file.anothername.ext2 [www.crap.com ].file.name.ext www.crap.com - file.name.ext $ ./sanitizeNames.sh *crap* $ ls -1 *crap* www.crap.com-file.anothername.ext2 www.crap.com-file.name-1.ext www.crap.com-file.name-2.ext www.crap.com-file.name-3.ext www.crap.com-file.name.ext 

Puedes usar rnm

 rnm -rs '/\[crap\]|\[spam\]//g' *.ext 

Lo anterior eliminará [crap] o [spam] del nombre de archivo.

Puede pasar múltiples patrones regex finalizándolos con ; o sobrecargando la opción -rs .

 rnm -rs '/[\[\]]//g;/\s*\[crap\]//g' -rs '/crap2//' *.ext 

El formato general de esta cadena de reemplazo es /search_part/replace_part/modifier

  1. search_part : regex para buscar.
  2. replace_part : string para reemplazar con
  3. modificador : i (sin distinción entre mayúsculas y minúsculas), g (reemplaza global)

Mayúscula minúscula:

Una cadena de reemplazo del /search_part/\c/modifier form /search_part/\c/modifier hará que la parte seleccionada del nombre del archivo (por el regex search_part ) en minúscula, mientras que \C (capital \ C) en la parte de reemplazo lo convertirá en mayúscula.

 rnm -rs '/[abcd]/\C/g' *.ext ## this will capitalize all a,b,c,d in the filenames 


Si tiene muchos patrones de expresiones regulares que deben tratarse, coloque esos patrones en un archivo y pase el archivo con la opción -rs/f .

 rnm -rs/f /path/to/regex/pattern/file *.ext 

Puedes encontrar algunos otros ejemplos aquí .

Nota:

  1. rnm usa PCRE2 (PCRE revisado) regex.
  2. Puede deshacer una operación de cambio de nombre no deseado ejecutando rnm -u

PD: soy el autor de esta herramienta.