Como posso organizar arquivos com base na primeira letra do nome do arquivo em pastas A-Z?

15

Estou procurando um caminho (preferencialmente terminal) para organizar mais de 1.000 fontes em sua primeira letra.

Basicamente, crie diretórios A-Z, # e mova os arquivos de fonte para esses diretórios com base no primeiro caractere do nome do arquivo. Fontes que começam com números [0-9] ou outros caracteres especiais a serem movidos para o diretório # .

    
por Parto 10.01.2017 / 10:44

6 respostas

13

Uma opção tardia do python:

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

Como usar

  1. Copie o script em um arquivo vazio, salve-o como move_files.py
  2. Execute-o com o diretório como argumento:

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

O script criará apenas o (sub) diretório (-ies) (maiúsculo) se for realmente necessário

Explicação

O script:

  • lista os arquivos, obtém o primeiro caractere (define o sourcepath):

     
    for f in os.listdir(dr):
        s = f[0]; fsrc = path(dr, f)
    
  • verifica se o item é um arquivo:

    if os.path.isfile(fsrc):
    
  • define a pasta de destino para um dos dois, se o primeiro caractere for alfa ou não:

    target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
    
  • verifica se a pasta já existe ou não, cria se não:

    if not os.path.exists(target):
        os.mkdir(target)
    
  • move o item para a pasta correspondente:

    shutil.move(fsrc, path(target, f))
    
por Jacob Vlijm 10.01.2017 / 14:31
11

Code-golfed ainda legível com apenas dois comandos e duas expressões regulares:

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

Se você tem uma quantidade enorme de arquivos para mover, muitos para caber na lista de argumentos do processo (sim, há um limite e pode ser de apenas alguns kilobytes), você pode gerar a lista de arquivos com um comando diferente e canalize isso para prename , e. g .:

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

Isso tem a vantagem de não tentar mover o nome do arquivo literal [[:alnum:]]?* se nenhum arquivo corresponder ao padrão glob. find também permite muito mais critérios de correspondência do que a globalização de conchas. Uma alternativa é definir a opção nullglob shell e fechar o fluxo de entrada padrão de prename . 1

Em ambos os casos, remova a opção -n para realmente mover os arquivos e não apenas mostrar como eles seriam movidos.

Adendo: Você pode remover os diretórios vazios novamente com:

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

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

    
por David Foerster 10.01.2017 / 21:25
8

Se você não se importa com zsh, uma função e alguns comandos zmv :

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

A função mmv cria o diretório e move o arquivo. zmv , em seguida, fornece correspondência e substituição de padrão. Primeiro, movendo nomes de arquivos começando com um alfabeto, então todo o resto:

$ zmv -P mmv '([a-zA-Z])(*.ttf)' '${(UC)1}/'
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)' '#/'
mkdir -p #/
mv -- 123.ttf #/123.ttf
mkdir -p #/
mv -- 七.ttf #/七.ttf

Execute novamente sem a definição de echo in mmv para realmente executar a movimentação.

    
por muru 10.01.2017 / 12:04
8

Eu não criei uma maneira legal de tornar os nomes dos diretórios em maiúsculas (ou mover os arquivos com letras maiúsculas), embora você possa fazer isso depois com 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 mais legível:

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

Remova echo após o teste para mover realmente os arquivos

E então

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

remova -n se parecer bom depois de testar e executar novamente.

    
por Zanna 10.01.2017 / 11:57
7

Os seguintes comandos no diretório que contém as fontes devem funcionar, se você quiser usar de fora do diretório de armazenamento de fontes, altere for f in ./* para for f in /directory/containing/fonts/* . Este é um método muito baseado em shell, muito lento, e também não é recursivo. Isso só criará diretórios, se houver arquivos que começam com o caractere correspondente.

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 um forro, novamente a partir do diretório de armazenamento de fontes:

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

Um método usando find, com manipulação de string semelhante, usando expansão de parâmetro bash, que será recursiva, e deve ser um pouco mais rápido que a versão 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' \;

Ou mais legível:

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' \;
    
por Arronical 10.01.2017 / 12:24
5

Mapeie cada nome de arquivo para um nome de diretório usando tr e, em seguida, mkdir e 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'
    
por xn. 10.01.2017 / 18:42