¿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.

Parto
fuente
¿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))
Jacob Vlijm
fuente
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 ... <&-

David Foerster
fuente
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.

muru
fuente
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.

Zanna
fuente
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' \;
Arronico
fuente
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'
xn.
fuente
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