Cambiar el nombre de gran cantidad de archivos de imagen con bash

16

Necesito cambiar el nombre de aprox. 70,000 archivos. Por ejemplo: de sb_606_HBO_DPM_0089000a sb_606_dpm_0089000etc.

El rango de números va de 0089000a 0163022. Es solo la primera parte del nombre que necesita cambiar. Todos los archivos están en un solo directorio y están numerados secuencialmente (una secuencia de imágenes). Los números deben permanecer sin cambios.

Cuando intento esto en bash, me sorprende que la "Lista de argumentos sea demasiado larga".

Editar:

Primero intenté renombrar un solo archivo con mv:

mv sb_606_HBO_DPM_0089000.dpx sb_606_dpm_0089000.dpx

Luego intenté renombrar un rango (aprendí aquí la semana pasada cómo mover una carga de archivos, así que pensé que la misma sintaxis podría funcionar para renombrar los archivos ...). Yo creo que probé el siguiente (o algo parecido):

mv sb_606_HBO_DPM_0{089000..163023}.dpx sb_606_dpm_0{089000..163023}.dpx
Rico
fuente
44
Para los revisores : no creo que sea un duplicado; La mayoría de las respuestas de CLI sobre la otra pregunta no funcionarán aquí debido a la gran cantidad de archivos que chocan con el ARG_MAXlímite del shell . Como esta pregunta solicita explícitamente una solución de línea de comandos, las soluciones GUI (posiblemente iguales) como en la otra pregunta tampoco coinciden.
postre
1
No creo que esto sea un engaño porque está bien tener más de una pregunta sobre el cambio de nombre de los archivos. Por favor, no cerremos preguntas específicas sobre recursos genéricos que realmente no las respondan ...
Zanna
1
@rich Si puedes editar explícitamente qué comando intentaste, entonces sería más claro que esto no es un engaño. (Esto nos muestra que conoce este enfoque). Saludos.
Sparhawk
2
rico, tu pregunta no es un engaño, porque es una pregunta específica. No te preocupes por eso. Más importante aún, después de que una pregunta haya recibido una cantidad de respuestas votadas, editarla probablemente no sea una buena idea porque sus ediciones pueden hacer que las respuestas existentes sean menos válidas. Ahora siento que mi respuesta debería explicar por qué mv {1..2} {3..4}no funciona, lo cual es un problema completamente diferente de ARG_MAX... ¡Todos los que respondieron probablemente sentirán lo mismo! Por lo tanto, desde mi punto de vista, desearía que revierta su última edición y, si lo desea, haga una pregunta completamente nueva acerca de mvlos rangos
Zanna
1
@Sparhawk, el OP escribió con bastante claridad, desde la primera versión de la pregunta, que el problema es el argument list too longerror. No hay necesidad de aclarar más, esto claramente no es una trampa ya que necesitamos una solución alternativa para tratar con ARG_MAX y las respuestas en el duplicado propuesto no lo hacen.
terdon

Respuestas:

25

Una forma es usar findcon -exec, y la +opción. Esto construye una lista de argumentos, pero divide la lista en tantas llamadas como sea necesario para operar en todos los archivos sin exceder la lista máxima de argumentos. Es adecuado cuando todos los argumentos serán tratados de la misma manera. Este es el caso con rename, aunque no con mv.

Es posible que deba instalar Perl rename:

sudo apt install rename

Entonces puedes usar, por ejemplo:

find . -maxdepth 1 -exec rename -n 's/_HBO_DPM_/_dpm_/' {} +

Eliminar -ndespués de la prueba, para cambiar el nombre de los archivos.

Zanna
fuente
11

Voy a sugerir tres alternativas. Cada uno es un comando simple de una sola línea, pero proporcionaré variantes para casos más complicados, principalmente en el caso de que los archivos a procesar se mezclen con otros archivos en el mismo directorio.

mmv

Me gustaría usar el comando MMV del paquete del mismo nombre :

mmv '*HBO_DPM*' '#1dpm#2'

Tenga en cuenta que los argumentos se pasan como cadenas, por lo que la expansión global no se produce en el shell. El comando recibe exactamente dos argumentos y luego encuentra los archivos correspondientes internamente, sin límites estrictos en la cantidad de archivos. También tenga en cuenta que el comando anterior asume que todos los archivos que coincidan con el primer glob serán renombrados. Por supuesto, eres libre de ser más específico:

mmv 'sb_606_HBO_DPM_*' 'sb_606_dpm_#1'

Si tiene archivos fuera del rango de números solicitados en el mismo directorio, podría ser mejor con el ciclo sobre los números que figuran más adelante en esta respuesta. Sin embargo, también podría usar una secuencia de invocaciones de mmv con patrones adecuados:

mmv 'sb_606_HBO_DPM_0089*'       'sb_606_dpm_0089#1'    # 0089000-0089999
mmv 'sb_606_HBO_DPM_009*'        'sb_606_dpm_009#1'     # 0090000-0099999
mmv 'sb_606_HBO_DPM_01[0-5]*'    'sb_606_dpm_01#1#2'    # 0100000-0159999
mmv 'sb_606_HBO_DPM_016[0-2]*'   'sb_606_dpm_016#1#2'   # 0160000-0162999
mmv 'sb_606_HBO_DPM_01630[01]?'  'sb_606_dpm_01630#1#2' # 0163000-0163019
mmv 'sb_606_HBO_DPM_016302[0-2]' 'sb_606_dpm_016302#1'  # 0163020-0163022

recorrer los números

Si desea evitar instalar cualquier cosa, o necesita seleccionar por rango de números evitando coincidencias fuera de este rango, y está preparado para esperar 74,023 invocaciones de comandos, puede usar un bucle bash simple:

for i in {0089000..0163022}; do mv sb_606_HBO_DPM_$i sb_606_dpm_$i; done

Esto funciona particularmente bien aquí ya que no hay espacios en la secuencia. De lo contrario, es posible que desee verificar si el archivo fuente realmente existe.

for i in {0089000..0163022}; do
  test -e sb_606_HBO_DPM_$i && mv sb_606_HBO_DPM_$i sb_606_dpm_$i
done

Tenga en cuenta que, en contraste con for ((i=89000; i<=163022; ++i))la expansión de llaves, maneja los ceros iniciales desde que se lanzó Bash hace un par de años. En realidad, solicité un cambio, así que estoy feliz de ver casos de uso.

Lecturas adicionales: Expansión de llaves en las páginas de información de Bash, particularmente la parte sobre {x..y[..incr]}.

recorrer los archivos

Otra opción sería recorrer un globo adecuado, en lugar de simplemente recorrer el rango entero en cuestión. Algo como esto:

for i in *HBO_DPM*; do mv "$i" "${i/HBO_DPM/dpm}"; done

Nuevamente, esta es una mvinvocación por archivo. Y de nuevo, el ciclo está sobre una larga lista de elementos, pero la lista completa no se pasa como argumento a un subproceso, sino que se maneja internamente por bash, por lo que el límite no le causará problemas.

Lectura adicional: Expansión de parámetros de Shell en las páginas de información de Bash, documentando ${parameter/pattern/string}entre otros.

Si desea restringir el rango de números al que proporcionó, puede agregar un cheque para eso:

for i in sb_606_HBO_DPM_+([0-9]); do
  if [[ "${i##*_*(0)}" -ge 89000 ]] && [[ "${i##*_*(0)}" -le 163022 ]]; then
    mv "$i" "${i/HBO_DPM/dpm}"
  fi
done

Aquí ${i##pattern}elimina la coincidencia de prefijo más larga patternde $i. El prefijo más largo se define como cualquier cosa, luego un guión bajo, luego cero o más ceros. Este último se escribe como *(0)un patrón de globo extendido que depende de la extglobopción que se configure. Eliminar los ceros a la izquierda es importante para tratar el número como base 10 y no como base 8. El +([0-9])argumento en el bucle es otro globo extendido, que coincide con uno o más dígitos, en caso de que tenga archivos allí que comiencen igual pero no terminen en un número.

MvG
fuente
¡Gracias! Esto funcionó como un sueño: para mí en {0089000..0163022}; do mv sb_606_HBO_DPM_ $ i sb_606_dpm_ $ i; hecho: tuve que agregar la extensión de nombre de archivo para que funcione, pero lo hizo exactamente como quería e incluso entiendo la sintaxis. Gracias @ MVG
rico
@rich: Feliz de poder ayudarte, tú y, con suerte, futuros visitantes también. No olvides aceptar la respuesta más útil. Siempre puede cambiar esa marca de verificación en el futuro si surge algo mejor.
MvG
10

Una forma de evitar el ARG_MAXlímite es usar el basil shell de bash printf:

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'

Ex.

rename -n 's/HBO_DPM/dpm/' sb_*
bash: /usr/bin/rename: Argument list too long

pero

printf '%s\0' sb_* | xargs -0 rename -n 's/HBO_DPM/dpm/'
rename(sb_606_HBO_DPM_0089000, sb_606_dpm_0089000)
.
.
.
rename(sb_606_HBO_DPM_0163022, sb_606_dpm_0163022)
conductor de acero
fuente
7
find . -type f -exec bash -c 'echo $1 ${1/HBO_DPM/dpm}' _ {} \;
./sb_606_HBO_DPM_0089000 ./sb_606_dpm_0089000

finden el directorio actual .para todos los archivos -type fy hacer cambiar el nombre del archivo que se encuentra $1con el reemplazo HBO_DPMde dmp uno por uno-exec ... \;

reemplace echocon mvpara realizar el cambio de nombre.

αғsнιη
fuente
6

Podrías escribir un pequeño script de Python, algo como:

import os
for file in os.listdir("."):
    os.rename(file, file.replace("HBO_DPM", "dpm"))

Guarde eso como un archivo de texto como rename.pyen la carpeta en la que están los archivos, luego con el terminal en esa carpeta vaya:

python rename.py
Calavera de piedra
fuente
6

Puede hacerlo archivo por archivo (puede llevar algo de tiempo) con

sudo apt install util-linux  # if you don't have it already
for i in *; do rename.ul HBO_DPM dpm "$i"; done

Al igual que el Perl renameusado en otras respuestas, rename.ultambién tiene una opción -no --no-actpara probar.

muclux
fuente
He editado tu comentario sobre la respuesta de Zanna, edita la respuesta de Zanna o deja un comentario.
fosslinux
@ubashu que no fue un comentario sobre mi respuesta, se refería a la -nbandera que usé para probar y sugirió que también se puede usar rename.ul.
Zanna
3

Veo que nadie ha invitado a mi mejor amigo seda la fiesta :). El siguiente forciclo logrará su objetivo:

for i in sb_606_HBO_DPM*; do
  mv "$i" "$(echo $i | sed 's/HBO_DPM/dpm/')";
done

Hay muchas herramientas para tal trabajo, seleccione la que sea más comprensible para usted. Este es simple y fácil de modificar para adaptarse a este u otros propósitos ...

andrew.46
fuente
De acuerdo, no es muy relevante en este caso específico, pero fallará si alguno de los nombres de archivo contiene nuevas líneas. Menciono esto ya que la mayoría (¿todas?) Otras respuestas son robustas y pueden tratar con nombres de archivos arbitrarios, o solo funcionan en el esquema de nombres de archivos del OP.
terdon
... nuevas líneas, espacios, comodines, ... algunos de los cuales se pueden evitar citando $ien la sustitución de comandos, pero no hay una manera fácil de manejar una nueva línea final en el nombre del archivo.
muru
3

Dado que estamos dando opciones, aquí hay un enfoque de Perl. cden el directorio de destino y ejecute:

perl -e 'foreach(glob("sb_*")){rename $_, s/_HBO_DPM_/_dpm_/r}'

Explicación

  • perl -e: ejecuta el script dado por -e.
  • foreach(glob){}: ejecuta lo que esté en { }cada resultado del globo.
  • glob("sb_*"): devuelve una lista de todos los archivos y directorios en el directorio actual cuyos nombres coinciden con el shell glob sb*.
  • rename $_, s/_HBO_DPM_/_dpm_/r: perl magic. $_es una variable especial que contiene cada elemento sobre el que estamos iterando (en el foreach). Entonces, aquí, se encontrará cada archivo. s/_HBO_DPM_/_dpm_/reemplaza la primera aparición de _HBO_DPM_con _dpm_. Se ejecuta $_de manera predeterminada, por lo que se ejecutará en cada nombre de archivo. El /rmedio "aplica este reemplazo a una copia de la cadena de destino (el nombre del archivo) y devuelve la cadena modificada. renameHace lo que esperaría: cambia el nombre de los archivos. Así que todo cambiará el nombre del archivo actual ( $_) a sí mismo con _HBO_DPM_reemplazado por _dpm_.

Podría escribir lo mismo que un script expandido (y más legible):

#! /usr/bin/env perl
use strict;
use warnings;

foreach my $fileName (glob("sb_*")){
  ## Copy the name to a new variable
  my $newName = $fileName;
  ## change the copy. $newName is now the changed version
  $newName =~ s/_HBO_DPM_/_dpm_/;
  ## rename
  rename $fileName, $newName;
}
terdon
fuente
1

Dependiendo del tipo de cambio de nombre que esté imaginando, usar vidir con edición de líneas múltiples puede ser satisfactorio.
En su caso particular, puede seleccionar todas las líneas en su editor de texto y eliminar la parte _ " HBO" de los nombres de archivo con pocas teclas.

kraymer
fuente
Sí, vi tiene encontrar y reemplazar gloable.
Jasen
2
¿Puede por favor extender su respuesta y dar un ejemplo de cómo lograr el objetivo de OP vidir?
postre