Come posso organizzare i file in base alla prima lettera del loro nome file nelle cartelle AZ


15

Sto cercando un modo (preferibilmente terminale) per organizzare oltre 1000 caratteri con la loro prima lettera.

Fondamentalmente creare directory A-Z, #quindi spostare i file dei caratteri in quelle directory in base al primo carattere del nome file. Caratteri che iniziano con numeri [0-9] o altri caratteri speciali da spostare nella #directory.


Vuoi che le directory vengano create anche se non ci sono file che iniziano con quella lettera?
Arronical

@Arronical Nope. Solo se ci sono file.
Parto,

Risposte:


13

Un'opzione di pitone in ritardo:

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

Come usare

  1. Copia lo script in un file vuoto, salvalo come move_files.py
  2. Eseguilo con la directory come argomento:

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

Lo script creerà la directory (sub) (-ies) (maiuscola) solo se è effettivamente necessario

Spiegazione

Il copione:

  • elenca i file, ottiene il primo carattere (definisce il sourcepath):

    for f in os.listdir(dr):
        s = f[0]; fsrc = path(dr, f)
  • controlla se l'articolo è un file:

    if os.path.isfile(fsrc):
  • definisce la cartella di destinazione per se il primo carattere è alpha o no:

    target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
  • controlla se la cartella esiste già o no, la crea in caso contrario:

    if not os.path.exists(target):
        os.mkdir(target)
  • sposta l'elemento nella cartella corrispondente:

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

Ehi Jacob. Esiste un controllo per le prime lettere maiuscole?
Parto,

@Parto Absolutely! In che modo ne hai bisogno? (Posso fare il check in 3,5 ore di insegnamento :)
Jacob Vlijm,

In realtà lo ha fatto. Implementazione perfetta.
Parto,

Dopo aver considerato, ho deciso di seguire questa risposta per diversi motivi: 1). La spiegazione di ciò che sta succedendo. 2). La risposta viene eseguita correttamente al primo tentativo
Parto

11

Code-golfed ma leggibile con solo due comandi e due espressioni regolari:

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

Se hai una grande quantità di file da spostare, troppi per adattarsi all'elenco degli argomenti del processo (sì, c'è un limite e potrebbe essere solo qualche kilobyte), puoi generare l'elenco dei file con un comando diverso e pipe che prename, per esempio:

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

Ciò ha l'ulteriore vantaggio di non provare a spostare il nome del file letterale [[:alnum:]]?*se nessun file corrisponde al modello glob. findconsente inoltre molti più criteri di corrispondenza rispetto al gorgogliamento delle coperture. Un'alternativa è impostare l' nullglobopzione shell e chiudere il flusso di input standard di prename. 1

In entrambi i casi rimuovere l' -ninterruttore per spostare effettivamente i file e non solo mostrare come sarebbero stati spostati.

Addendum: è possibile rimuovere nuovamente le directory vuote con:

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

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


8

Se non ti dispiace zsh, una funzione e un paio di zmvcomandi:

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 mmvfunzione crea la directory e sposta il file. zmvquindi fornisce la corrispondenza e la sostituzione del modello. Innanzitutto, spostando i nomi dei file che iniziano con un alfabeto, quindi tutto il resto:

$ 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

Eseguire di nuovo senza la echoa mmv's definizione realmente eseguire lo spostamento.


8

Non ho trovato un modo carino per rendere maiuscoli i nomi delle directory (o spostare i file con lettere maiuscole), anche se in seguito potresti farlo 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 più facilmente:

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

Rimuovere echodopo il test per spostare effettivamente i file

E poi

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

rimuovere -nse sembra buono dopo il test ed eseguire di nuovo.


2
Puoi usare if [[ -d "${i^}" ]]per rendere il icapitale variabile e mkdir {A..Z}all'inizio.
Arronical,

Fammi provare e vedere
Parto,

@Arronical grazie! Lo lascerò comunque, dal momento che l'hai pubblicato a modo tuo
Zanna,

@Zanna Mi piace che ci siamo arrivati ​​da direzioni diverse, ripetendo le lettere e usandole come criteri di ricerca non mi era mai venuto in mente. Sono sicuro che esiste una soluzione intelligente e veloce con find, ma non riesco a pensarci!
Arronical

Ehi Zanna, questo non ha spostato i caratteri che iniziano con una lettera maiuscola. Altrimenti ha funzionato bene.
Parto,

7

I seguenti comandi all'interno della directory contenente i caratteri dovrebbero funzionare, se si desidera utilizzare al di fuori della directory di archiviazione dei caratteri, passare for f in ./*a for f in /directory/containing/fonts/*. Questo è un metodo molto basato sulla shell, quindi piuttosto lento, ed è anche non ricorsivo. Questo creerà directory solo se ci sono file che iniziano con il carattere corrispondente.

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

Come una riga, sempre dalla directory di archiviazione dei caratteri:

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 metodo che usa find, con una manipolazione di stringhe simile, usa l'espansione dei parametri bash, che sarà ricorsiva e dovrebbe essere un po 'più veloce della versione pura della 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' \;

O più facilmente:

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

Anche questo ha funzionato. Anche per lettere maiuscole e minuscole.
Parto,

5

Mappare ciascun nome file su un nome di directory utilizzando tr, quindi mkdire 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'

Mi piace molto, questo è sulla falsariga di ciò che stavo afferrando con il mio desiderio di usare find. C'è un modo per creare solo nomi di directory in maiuscolo e spostare i nomi dei file in minuscolo?
Arronical

1
Ne ho aggiunto un altro trda convertire in maiuscolo.
xn.

Perché la deviazione attraverso xargssolo per chiamare di bashnuovo? Non sarebbe più semplice e molto più leggibile eseguire il pipe dell'output findin un ciclo while e readlì record per record?
David Foerster,

Bella domanda, @DavidFoerster. Immagino il pregiudizio contro i loop in una linea e la preferenza per un approccio più funzionale. Ma uno script bash in una stringa non è neanche molto elegante, quindi direi che la whileversione loop ( bit.ly/2j2mhyb ) è forse migliore.
xn.

Tra l'altro, è possibile evitare la brutta {}sostituzione se si lascia che xargsaggiungere l'argomento e fare riferimento alla $1all'interno dello script di shell, ad esempio: xargs -0 -n1 -- bash -c 'dir=/dest/$(basename "$1" | ...); ...; mv "$1" "$dir"' _. (Mind the final _!)
David Foerster,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.