Jak zorganizować pliki na podstawie ich pierwszej litery w folderze AZ

15

Szukam sposobu (najlepiej terminalu) uporządkowania ponad 1000 czcionek według ich pierwszej litery.

Zasadniczo utwórz katalogi, A-Z, #a następnie przenieś pliki czcionek do tych katalogów na podstawie pierwszego znaku nazwy pliku. Czcionki rozpoczynające się cyframi [0–9] lub innymi znakami specjalnymi, które mają zostać przeniesione do #katalogu.

Parto
źródło
Czy chcesz, aby katalogi były tworzone, nawet jeśli nie ma plików zaczynających się od tej litery?
Arroniczny
@Arronical Nope. Tylko jeśli są pliki.
Parto
4
Mam nadzieję, że ten link pomoże Ci stackoverflow.com/questions/1251938/…
Karthickeyan

Odpowiedzi:

13

Późna opcja Pythona:

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

Jak używać

  1. Skopiuj skrypt do pustego pliku i zapisz go jako move_files.py
  2. Uruchom go z katalogiem jako argumentem:

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

Skrypt utworzy (pod) katalog (-ies) (wielkie litery) tylko wtedy, gdy jest rzeczywiście potrzebny

Wyjaśnienie

Scenariusz:

  • wyświetla listę plików, pobiera pierwszy znak (definiuje sourcepath):

    for f in os.listdir(dr):
        s = f[0]; fsrc = path(dr, f)
  • sprawdza, czy element jest plikiem:

    if os.path.isfile(fsrc):
  • definiuje docelowy folder dla pierwszego znaku, który ma wartość alfa, lub nie:

    target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
  • sprawdza, czy folder już istnieje, czy go tworzy, jeśli nie:

    if not os.path.exists(target):
        os.mkdir(target)
  • przenosi element do odpowiedniego folderu:

    shutil.move(fsrc, path(target, f))
Jacob Vlijm
źródło
Hej Jacob. Czy jest zaznaczone pierwsze wielkie litery?
Parto
@Parto Absolutnie! W jaki sposób tego potrzebujesz? (Mogę zameldować się za 3,5 godziny nauczania :)
Jacob Vlijm,
Rzeczywiście tak było. Idealne wdrożenie.
Parto
Po rozważeniu zdecydowałem się na tę odpowiedź z kilku powodów: 1). Wyjaśnienie, co się dzieje. 2). Odpowiedź działa poprawnie przy pierwszej próbie
Parto
11

Gra w golfa, ale czytelny za pomocą tylko dwóch poleceń i dwóch wyrażeń regularnych:

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

Jeśli masz dużą liczbę plików do przeniesienia, zbyt wiele, aby zmieściły się na liście argumentów procesu (tak, istnieje limit i może to być zaledwie kilka kilobajtów), możesz wygenerować listę plików za pomocą innego polecenia i potoku, aby prename, np .:

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

Ma to tę dodatkową zaletę, że nie próbuje przenieść literalnej nazwy pliku, [[:alnum:]]?*jeśli żaden plik nie pasuje do wzorca globu. findpozwala również na wiele więcej kryteriów dopasowania niż globowanie powłoki. Alternatywą jest ustawienie nullglobopcji powłoki i zamknięcie standardowego strumienia wejściowego prename. 1

W obu przypadkach usuń -nprzełącznik, aby faktycznie przenieść pliki, a nie tylko pokazać, jak zostaną przeniesione.

Dodatek: Możesz ponownie usunąć puste katalogi za pomocą:

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

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

David Foerster
źródło
8

Jeśli nie masz nic przeciwko zsh, funkcja i kilka zmvpoleceń:

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'

mmvFunkcja sprawia, że katalog i przenosi plik. zmvnastępnie zapewnia dopasowanie wzorca i podstawienie. Najpierw przenoszenie nazw plików zaczynających się od alfabetu, a następnie wszystko inne:

$ 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

Uruchom ponownie bez echow mmv„s definicji faktycznie wykonać ruch.

muru
źródło
8

Nie opracowałem dobrego sposobu, aby nazwy katalogów były pisane wielkimi literami (lub przenosić pliki dużymi literami), chociaż można to zrobić później za pomocą 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

lub bardziej czytelnie:

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

Usuń echopo przetestowaniu, aby faktycznie przenieść pliki

I wtedy

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

usuń, -njeśli po testach wygląda dobrze i uruchom ponownie.

Zanna
źródło
2
Na początek możesz użyć kapitału if [[ -d "${i^}" ]]zmiennego . imkdir {A..Z}
Arroniczny
Pozwól, że spróbuję i zobaczę
Parto
@Arronical dzięki! Zostawię to jednak, ponieważ opublikowałeś go na swój własny sposób
Zanna
@Zanna Podoba mi się, że doszliśmy do tego z różnych stron, iterując litery i używając ich jako kryteriów wyszukiwania, nigdy nie przyszło mi do głowy. Jestem pewien, że istnieje sprytne i szybkie rozwiązanie ze znalezieniem, ale nie mogę tego obejść!
Arronical
Hej Zanna, to nie przesunęło czcionek zaczynających się na wielką literę. W przeciwnym razie działało dobrze.
Parto
7

Poniższe polecenia w katalogu zawierającym czcionki powinny działać, jeśli chcesz używać spoza katalogu przechowywania czcionek, zmień for f in ./*na for f in /directory/containing/fonts/*. Jest to metoda bardzo oparta na powłoce, więc dość powolna, a także nierekurencyjna. Spowoduje to utworzenie katalogów tylko wtedy, gdy istnieją pliki, które zaczynają się od pasującego znaku.

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

Jako jeden wiersz, ponownie z katalogu przechowywania czcionek:

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

Metoda wykorzystująca find, z podobną manipulacją ciągiem, wykorzystująca interpretację parametrów bash, która będzie rekurencyjna i powinna być nieco szybsza niż wersja czysto powłoki:

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

Lub bardziej czytelnie:

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' \;
Arroniczny
źródło
Ten też działał. Nawet dla wielkich i małych liter.
Parto
5

Zamapuj każdą nazwę pliku na nazwę katalogu, używając tr, a następnie mkdiri 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.
źródło
Naprawdę podoba mi się to, jest to zgodne z tym, co chwytałem za moją chęć użycia find. Czy istnieje sposób na tworzenie tylko nazw katalogów pisanych wielkimi literami i przenoszenie do nich nazw plików pisanych małymi literami?
Arroniczny
1
Dodałem kolejny trdo konwersji na wielkie litery.
xn.
Po co objazd, xargsaby zadzwonić bashponownie? Czy nie byłoby prostsze i znacznie bardziej czytelne umieszczanie danych wyjściowych findw pętli while i readzapisywanie ich po rekordzie?
David Foerster,
Dobre pytanie, @DavidFoerster. Wydaje mi się, że uprzedzenie do pętli w liniach jednokreskowych i preferuję bardziej funkcjonalne podejście. Ale skrypt bash w ciągu również nie jest zbyt elegancki, więc powiedziałbym, że whilewersja pętli ( bit.ly/2j2mhyb ) jest być może lepsza.
xn.
Nawiasem mówiąc, możesz uniknąć nieprzyjemnego {}podstawienia, jeśli pozwolisz xargsdołączyć argument, a następnie odwołać się do $1skryptu powłoki, np xargs -0 -n1 -- bash -c 'dir=/dest/$(basename "$1" | ...); ...; mv "$1" "$dir"' _. : (Pamiętaj o finale _!)
David Foerster,