Comment puis-je organiser les fichiers en fonction de leur première lettre de nom de fichier dans des dossiers AZ

15

Je cherche un moyen (de préférence terminal) d'organiser plus de 1000 polices par leur première lettre.

Fondamentalement, créez des répertoires, A-Z, #puis déplacez les fichiers de police vers ces répertoires en fonction du premier caractère de leur nom de fichier. Polices commençant par des chiffres [0-9] ou d'autres caractères spéciaux à déplacer vers le #répertoire.

Parto
la source
Voulez-vous que les répertoires soient créés même s'il n'y a aucun fichier commençant par cette lettre?
Arronical
@Arronical Nope. Juste s'il y a des fichiers.
Parto
4
J'espère que ce lien vous aidera à stackoverflow.com/questions/1251938/…
Karthickeyan

Réponses:

13

Une option de python tardif:

#!/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))

Comment utiliser

  1. Copiez le script dans un fichier vide, enregistrez-le sous move_files.py
  2. Exécutez-le avec le répertoire comme argument:

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

Le script ne crée le (sous) répertoire (-ies) (majuscule) que s'il est réellement nécessaire

Explication

Le script:

  • liste les fichiers, obtient le premier caractère (définit le chemin source):

    for f in os.listdir(dr):
        s = f[0]; fsrc = path(dr, f)
  • vérifie si l'élément est un fichier:

    if os.path.isfile(fsrc):
  • définit le dossier cible pour si le premier caractère est alpha ou non:

    target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
  • vérifie si le dossier existe déjà ou non, le crée sinon:

    if not os.path.exists(target):
        os.mkdir(target)
  • déplace l'élément dans son dossier correspondant:

    shutil.move(fsrc, path(target, f))
Jacob Vlijm
la source
Salut Jacob. Existe-t-il une vérification pour les premières lettres majuscules?
Parto
@Parto Absolument! De quelle façon en avez-vous besoin? (Je peux enregistrer 3,5 heures d'enseignement :)
Jacob Vlijm
C'est effectivement le cas. Implémentation parfaite.
Parto
Après réflexion, j'ai décidé d'aller avec cette réponse pour plusieurs raisons: 1). L'explication de ce qui se passe. 2). La réponse a fonctionné correctement au premier essai
Parto
11

Joué au code mais lisible avec seulement deux commandes et deux expressions régulières:

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

Si vous avez une énorme quantité de fichiers à déplacer, trop nombreux pour tenir dans la liste des arguments du processus (oui, il y a une limite et cela peut être seulement quelques kilo-octets), vous pouvez générer la liste des fichiers avec une commande et un canal différents pour prename, par exemple:

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

Cela a l'avantage supplémentaire de ne pas essayer de déplacer le nom de fichier littéral [[:alnum:]]?*si aucun fichier ne correspond au modèle global. findpermet également beaucoup plus de critères de correspondance que la globalisation du shell. Une alternative consiste à définir l' nullgloboption shell et à fermer le flux d'entrée standard de prename. 1

Dans les deux cas, supprimez le -ncommutateur pour déplacer réellement les fichiers et pas seulement montrer comment ils seraient déplacés.

Addendum: vous pouvez à nouveau supprimer les répertoires vides avec:

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

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

David Foerster
la source
8

Si cela ne vous dérange pas zsh, une fonction et quelques zmvcommandes:

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 mmvfonction crée le répertoire et déplace le fichier. zmvfournit ensuite le filtrage et la substitution de motifs. Tout d'abord, déplacer les noms de fichiers commençant par un alphabet, puis tout le reste:

$ 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

Exécutez à nouveau sans echodans mmvla définition de » pour réaliser effectivement le mouvement.

muru
la source
8

Je n'ai pas trouvé une bonne façon de mettre les noms de répertoires en majuscules (ou de déplacer les fichiers avec des lettres majuscules), bien que vous puissiez le faire ensuite avec 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

ou plus lisible:

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

Supprimer echoaprès les tests pour déplacer réellement les fichiers

Puis

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

supprimer -nsi cela semble bon après le test et réexécuter.

Zanna
la source
2
Vous pouvez utiliser if [[ -d "${i^}" ]]pour faire le icapital variable , et mkdir {A..Z}au début.
Arronical
Permettez-moi d'essayer cela et de voir
Parto
@Arronical thanks! Je vais le laisser cependant, puisque vous l'avez affiché à votre façon
Zanna
@Zanna J'aime que nous y soyons venus de différentes directions, en parcourant les lettres et en les utilisant comme critères de recherche ne m'était jamais venu à l'esprit. Je suis sûr qu'il existe une solution intelligente et rapide avec find, mais je ne peux pas m'y mettre!
Arronical
Hé Zanna, cela n'a pas déplacé les polices commençant par une lettre majuscule. Sinon, ça a bien fonctionné.
Parto
7

Les commandes suivantes dans le répertoire contenant les polices devraient fonctionner, si vous souhaitez utiliser à l'extérieur du répertoire de stockage des polices, passez for f in ./*à for f in /directory/containing/fonts/*. Il s'agit d'une méthode très basée sur le shell, donc assez lente et non récurrente. Cela ne créera des répertoires que s'il existe des fichiers commençant par le caractère correspondant.

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

Comme une ligne, à nouveau à partir du répertoire de stockage des polices:

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

Une méthode utilisant find, avec une manipulation de chaîne similaire, utilisant l'expansion des paramètres bash, qui sera récursive, et devrait être un peu plus rapide que la version pure shell:

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' \;

Ou plus lisible:

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' \;
Arronical
la source
Celui-ci a également fonctionné. Même pour les lettres majuscules et minuscules.
Parto
5

Mappez chaque nom de fichier à un nom de répertoire en utilisant tr, puis mkdiret 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.
la source
J'aime vraiment ça, c'est dans le sens de ce que je saisissais avec mon souhait d'utiliser find. Existe-t-il un moyen de créer uniquement des noms de répertoires en majuscules et d'y déplacer des noms de fichiers en minuscules?
Arronical
1
J'en ai ajouté un autre trpour convertir en majuscules.
xn.
Pourquoi faire le détour xargsjuste pour rappeler bash? Ne serait-il pas plus simple et beaucoup plus lisible de diriger la sortie de finddans une boucle while et d' ready enregistrer enregistrement par enregistrement?
David Foerster
Bonne question, @DavidFoerster. Je suppose que le parti pris contre les boucles dans les lignes simples et la préférence pour une approche plus fonctionnelle. Mais un script bash dans une chaîne n'est pas très élégant non plus, donc je dirais que la whileversion en boucle ( bit.ly/2j2mhyb ) est peut-être meilleure.
xn.
Soit dit en passant, vous pouvez éviter la mauvaise {}substitution si vous laissez xargsappend l'argument puis reportez - vous à l' $1intérieur du script shell, par exemple: xargs -0 -n1 -- bash -c 'dir=/dest/$(basename "$1" | ...); ...; mv "$1" "$dir"' _. (Attention à la finale _!)
David Foerster