Cuaderno de GNUtas

Una función para renombrar y numerar archivos marcados en Dired (Emacs)

GNU Emacs, más que un editor de texto que aspira a ser un sistema operativo (como tantas veces reza la consabida cantinela), es un editor de texto tremendamente consecuente con aquello para que fue concebido: editar texto. Cualquier cosa la entiende como un texto susceptible de ser editado, incluyendo los archivos de su navegador de directorios y ficheros, conocido como Dired. Podemos entrar en Dired, por ejemplo, y activar el modo de edición (teclas C-x C-q). Acto seguido, seremos capaces de modificar cualquier elemento de lo que tenemos allí delante como si fuese un simple documento de texto plano. Imagínense una utopía así, comparada con cualquier navegador de archivos gráfico, o incluso en la terminal. Cualquier propiedad de edición, navegación, manipulación de texto de Emacs es aquí libremente aplicable. Por ejemplo, si queremos renombrar una serie de archivos sucesivos, y además numerarlos, podemos echar mano de la funcionaldad rectangles, que permite crear regiones de texto rectangulares y editarlas de varias maneras.

Todo eso está muy bien pero, al margen de lo dicho, me preguntaba estos días si sería posible escribir alguna función que renombrase y numerase una pila de archivos marcados en Dired, una actividad que suelo hacer a menudo, sobre todo con las fotos. El esquema de esa función vendría a ser, a grandes trazos, el siguiente:

  1. Recogería en una lista los nombres de los archivos que ya tenemos marcados (se pulsa m con el cursor en cada archivo y así se marca para efectuar sobre éste operaciones varias), pero les quitaría la extensión.
  2. Con esa lista se crearía una nueva lista que incluyese todos los archivos con el nuevo nombre y numerados. Aquí la expresión mapcar (que ejecuta una función sobre cada uno de los elementos de una lista, por riguroso orden, y nos devuelve otra lista con el resultado) nos viene como anillo al dedo. En esta parte del proceso, también sería necesario definir un contador que numerara cada elemento.
  3. Habría que concatenar ahora ambas listas en una tercera, pero intercalando sus elementos. De tal forma que si la lista uno es '("manzanas" "peras" "sandías") y la lista dos '("frutas1" "frutas2" "frutas3"), la lista resultante fuese '("manzanas" "frutas1" "peras" "frutas2" "sandías" "frutas3").
  4. Y ahora viene la triquiñuela. Antes de concatenar las dos listas, haremos que a cada elemento de la primera le preceda mv (que no es otro que el comando de shell); y a cada elemento de la segunda le siga un salto de línea. Si convertimos la lista concatenada en una cadena de texto, obtendremos "mv manzanas frutas1 <corte de línea>, etc.", algo perfectamente inteligible si lo pasamos al final como argumento de la expresión shell-command.

Y, dicho esto, aquí tendríamos nuestra función resultante.

;; esta función previa crea una lista de los archivos marcados pero sin la extensión

(defun archivos-sin-extension ()
  (mapcar 'file-name-sans-extension (dired-get-marked-files)))

;; Definimos un contador
(setq contador 0)

; y la función propiamente dicha
(defun renombra-y-numera-dired ()
  (interactive)
  (let
;; Definimos tres variables para let. La tercera sería innecasaria si afinásemos más las expresiones regulares.
      ((lista-archivos (archivos-sin-extension))
        (nombre-archivos (read-from-minibuffer "¿Renombrar como?"))
        (extension (read-from-minibuffer "¿Extensión?")))
;; La nueva lista que genera los elementos renombrados y numerados
    (setq lista-nueva
          (mapcar
           (lambda (item)
             (replace-regexp-in-string ".+" (concat (format "%s" nombre-archivos) "_" (format "%s" (number-to-string (setf contador (+ contador 1)))) "\." (format "%s" extension)) item))
           lista-archivos))
;; Modificamos los elementos de las listas antes de concatenarlas
;; (introducimos el comando de shell "mv" y los saltos de línea"
    (setq lista-vieja (mapcar (lambda (item) (replace-regexp-in-string "\\(.+\\)" "mv \\1 " item)) (dired-get-marked-files)))
    (setq lista-nueva (mapcar (lambda (item) (replace-regexp-in-string "\\(.+\\)" "\\1\n" item)) lista-nueva))
;; Y las concatenamos:
    (setq lista-res (-interleave lista-vieja lista-nueva))
;; Ya sólo queda pasar la lista anterior, convertida en cadena de
;; texto como argumento de `shell-command'
    (shell-command
     (mapconcat 'identity lista-res " "))
;; Y, por último, pasamos a "nil" las tres listas, para reustilizar la función ;-)
    (setq lista-nueva nil)
    (setq lista-vieja nil)
    (setq lista-res nil)
    (setq contador 0)
    ))

; y un atajo para Dired
(with-eval-after-load 'dired
  (define-key dired-mode-map (kbd "C-x 7") 'renombra-y-numera-dired))

Y, como un gif vale más que mil palabras:

renombra_dired.gif

Publicado: 04/08/2019

Última actualización: 04/08/2019


Índice general

Acerca de...

Esta obra está bajo una licencia de Creative Commons Reconocimiento-NoComercial 4.0 Internacional.

© Juan Manuel Macías
Creado con esmero en
GNU Emacs