¿Cómo puedo organizar archivos basados ​​en la primera letra de su nombre de archivo en carpetas AZ


15

Estoy buscando una forma (preferiblemente terminal) para organizar más de 1000 fuentes por su primera letra.

Básicamente, cree directorios y A-Z, #luego mueva los archivos de fuente a esos directorios según el primer carácter de su nombre de archivo. Fuentes que comienzan con números [0-9] u otros caracteres especiales para mover al #directorio.


¿Desea que se realicen los directorios incluso si no hay archivos que comiencen con esa letra?
Arronical

@ Arronical Nope. Solo si hay archivos.
Parto

44
Espero que este enlace te ayude a stackoverflow.com/questions/1251938/…
Karthickeyan

Respuestas:


13

Una opción de Python tardío:

#!/usr/bin/env python3
import os
import sys
import shutil

def path(dr, f): return os.path.join(dr, f)

dr = sys.argv[1]
for f in os.listdir(dr):
    fsrc = path(dr, f)
    if os.path.isfile(fsrc):
        s = f[0]; target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
        if not os.path.exists(target):
            os.mkdir(target)
        shutil.move(fsrc, path(target, f))

Cómo utilizar

  1. Copie el script en un archivo vacío, guárdelo como move_files.py
  2. Ejecútelo con el directorio como argumento:

    python3 /path/to/move_files.py /path/to/files
    

El script solo creará el (sub) directorio (-ies) (mayúscula) si es realmente necesario

Explicación

La secuencia de comandos:

  • enumera los archivos, obtiene el primer carácter (define la ruta de origen):

    for f in os.listdir(dr):
        s = f[0]; fsrc = path(dr, f)
  • comprueba si el artículo es un archivo:

    if os.path.isfile(fsrc):
  • define la carpeta de destino para si el primer carácter es alfa o no:

    target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
  • comprueba si la carpeta ya existe o no, la crea si no:

    if not os.path.exists(target):
        os.mkdir(target)
  • mueve el elemento a su carpeta correspondiente:

    shutil.move(fsrc, path(target, f))

Hola Jacob ¿Hay un cheque para las primeras letras mayúsculas?
Parto

@Parto ¡Absolutamente! ¿De qué manera lo necesitas? (Puedo verificar 3,5 horas de enseñanza :)
Jacob Vlijm

En realidad lo hizo. Implementación perfecta.
Parto

Después de considerar, he decidido seguir con esta respuesta por varias razones: 1). La explicación de lo que está pasando. 2) La respuesta se ejecutó correctamente en el primer intento
Parto el

11

Código de golf pero legible con solo dos comandos y dos expresiones regulares:

mkdir -p '#' {a..z}
prename -n 's|^[[:alpha:]]|\l$&/$&|; s|^[0-9]|#/$&|' [[:alnum:]]?*

Si tiene una gran cantidad de archivos para mover, demasiados para caber en la lista de argumentos del proceso (sí, hay un límite y puede ser solo unos pocos kilobytes), puede generar la lista de archivos con un comando diferente y canalizar eso a prename, p.ej:

find -mindepth 1 -maxdepth 1 -name '[[:alnum:]]?*' -printf '%f\n' |
prename -n 's|^[[:alpha:]]|\l$&/$&|; s|^[0-9]|#/$&|'

Esto tiene el beneficio adicional de no intentar mover el nombre literal del archivo [[:alnum:]]?*si ningún archivo coincide con el patrón global. findtambién permite muchos más criterios de coincidencia que el globping de shell. Una alternativa es establecer la nullglobopción de shell y cerrar el flujo de entrada estándar de prename. 1

En ambos casos, quite el -ninterruptor para mover realmente los archivos y no solo muestre cómo se moverían.

Anexo: Puede eliminar los directorios vacíos nuevamente con:

rmdir --ignore-fail-on-non-empty '#' {a..z}

1 shopt -s nullglob; prename ... <&-


8

Si no le importa zsh, una función y un par de zmvcomandos:

mmv() {echo mkdir -p "${2%/*}/"; echo mv -- "$1" "$2";}
autoload -U zmv
zmv -P mmv '([a-zA-Z])(*.ttf)' '${(UC)1}/$1$2'
zmv -P mmv '([!a-zA-Z])(*.ttf)' '#/$1$2'

La mmvfunción crea el directorio y mueve el archivo. zmvluego proporciona coincidencia de patrones y sustitución. Primero, moviendo los nombres de archivo que comienzan con un alfabeto, luego todo lo demás:

$ zmv -P mmv '([a-zA-Z])(*.ttf)' '${(UC)1}/$1$2'
mkdir -p A/
mv -- abcd.ttf A/abcd.ttf
mkdir -p A/
mv -- ABCD.ttf A/ABCD.ttf
$ zmv -P mmv '([!a-zA-Z])(*.ttf)' '#/$1$2'
mkdir -p #/
mv -- 123.ttf #/123.ttf
mkdir -p #/
mv -- 七.ttf #/七.ttf

Ejecute nuevamente sin la definición de echoin mmvpara realizar el movimiento.


8

No encontré una buena manera de poner los nombres de directorio en mayúsculas (o mover los archivos con letras mayúsculas), aunque luego podría hacerlo con rename...

mkdir {a..z} \#; for i in {a..z}; do for f in "$i"*; do if [[ -f "$f" ]]; then echo mv -v -- "$f" "$i"; fi; done; done; for g in [![:alpha:]]*; do if [[ -f "$g" ]]; then echo mv -v -- "$g" \#; fi; done

o más legible:

mkdir {a..z} \#; 
for i in {a..z}; do 
  for f in "$i"*; do
    if [[ -f "$f" ]]; then 
      echo mv -v -- "$f" "$i"; 
    fi 
  done
done
for g in [![:alpha:]]*; do 
  if [[ -f "$g" ]]; then 
    echo mv -v -- "$g" \#
  fi
done

Eliminar echodespués de probar para mover realmente los archivos

Y entonces

rename -n 'y/[a-z]/[A-Z]/' *

eliminar -nsi se ve bien después de la prueba y ejecutar de nuevo.


2
Puedes usar if [[ -d "${i^}" ]]para hacer la variable icapital, y mkdir {A..Z}al principio.
Arronico

Déjame probar esto y ver
Parto

@Arronical gracias! Sin embargo, lo dejaré, ya que lo publicaste a tu manera
Zanna

@Zanna Me gusta que lo hicimos desde diferentes direcciones, iterando a través de las letras y usándolas como criterios de búsqueda que nunca se me habían ocurrido. Estoy seguro de que hay una solución inteligente y rápida con find, ¡pero no puedo entenderlo!
Arronico

Hola Zanna, esto no movió las fuentes que comienzan con una letra mayúscula. De lo contrario funcionó bien.
Parto

7

Los siguientes comandos dentro del directorio que contiene las fuentes deberían funcionar, si desea usarlos desde fuera del directorio de almacenamiento de fuentes, cambie for f in ./*a for f in /directory/containing/fonts/*. Este es un método muy basado en shell, muy lento, y también no es recursivo. Esto solo creará directorios, si hay archivos que comienzan con el carácter correspondiente.

target=/directory/to/store/alphabet/dirs
mkdir "$target"
for f in ./* ; do 
  if [[ -f "$f" ]]; then 
    i=${f##*/}
    i=${i:0:1}
    dir=${i^}
    if [[ $dir != [A-Z] ]]; then 
      mkdir -p "${target}/#" && mv "$f" "${target}/#"
    else
      mkdir -p "${target}/$dir" && mv "$f" "${target}/$dir"
    fi
  fi
done

Como una línea, nuevamente desde el directorio de almacenamiento de fuentes:

target=/directory/to/store/alphabet/dirs; mkdir "$target" && for f in ./* ; do if [[ -f "$f" ]]; then i=${f##*/}; i=${i:0:1} ; dir=${i^} ; if [[ $dir != [A-Z] ]]; then mkdir -p "${target}/#" && mv "$f" "${target}/#"; else mkdir -p "${target}/$dir" && mv "$f" "${target}/$dir" ; fi ; fi ; done

Un método que utiliza find, con una manipulación de cadenas similar, que utiliza la expansión de parámetros bash, que será recursivo y debería ser algo más rápido que la versión de shell pura:

find . -type f -exec bash -c 'target=/directory/to/store/alphabet/dirs ; mkdir -p "$target"; f="{}" ; i="${f##*/}"; i="${i:0:1}"; i=${i^}; if [[ $i = [[:alpha:]] ]]; then mkdir -p "${target}/$i" && mv "$f" "${target}/$i"; else mkdir -p "${target}/#" && mv "$f" "${target}/#"; fi' \;

O más legible:

find . -type f -exec bash -c 'target=/directory/to/store/alphabet/dirs 
   mkdir -p "$target"
   f="{}"
   i="${f##*/}"
   i="${i:0:1}"
   i=${i^}
   if [[ $i = [[:alpha:]] ]]; then 
      mkdir -p "${target}/$i" && mv "$f" "${target}/$i"
   else
      mkdir -p "${target}/#" && mv "$f" "${target}/#"
   fi' \;

Este también funcionó. Incluso para letras mayúsculas y minúsculas.
Parto

5

Asigne cada nombre de archivo a un nombre de directorio usando tr, luego mkdiry mv:

find /src/dir -type f -print0 |
xargs -0 -I{} bash -c \
  'dir=/dest/$(basename "{}" | cut -c1 | tr -C "a-zA-Z\n" "#" | tr "a-z "A-Z"); mkdir -p $dir; mv "{}" $dir'

Realmente me gusta esto, está en la línea de lo que estaba buscando con mi deseo de usar find. ¿Hay alguna manera de crear solo nombres de directorio en mayúsculas y mover nombres de archivo en minúsculas?
Arronical

1
Agregué otro trpara convertir a mayúsculas.
xn.

¿Por qué el desvío xargssolo para bashvolver a llamar ? ¿No sería más simple y mucho más legible canalizar la salida finden un bucle while y readgrabarla por grabación?
David Foerster

Buena pregunta, @DavidFoerster. Supongo que sesgo en contra de bucles en una línea y preferencia por un enfoque más funcional. Pero un script bash en una cadena tampoco es muy elegante, por lo que diría que la whileversión de bucle ( bit.ly/2j2mhyb ) es quizás mejor.
xn.

Por cierto, se puede evitar la desagradable {}sustitución si se deja xargsappend el argumento y luego consulte $1el interior de la secuencia de comandos shell, por ejemplo: xargs -0 -n1 -- bash -c 'dir=/dest/$(basename "$1" | ...); ...; mv "$1" "$dir"' _. (¡Cuidado con la final _!)
David Foerster
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.