Como posso organizar arquivos com base na primeira letra do nome do arquivo em pastas AZ


15

Estou procurando uma maneira (de preferência terminal) para organizar mais de 1000 fontes pela 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.


Deseja que os diretórios sejam criados mesmo que não haja arquivos começando com essa letra?
Arronical

@ Arronical Nope. Apenas se houver arquivos.
Parto

4
Espero que este link o ajude a stackoverflow.com/questions/1251938/…
Karthickeyan

Respostas:


13

Uma opção de python atrasada:

#!/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 (s) (maiúsculo) se for realmente necessário

Explicação

O script:

  • lista os arquivos, obtém o primeiro caractere (define o caminho de origem):

    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 se o primeiro caractere é 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-a se não:

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

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

Hey Jacob. Existe uma verificação para as primeiras letras maiúsculas?
Parto

@Parto Absolutely! De que maneira você precisa? (I pode verificar em 3,5 horas de ensino :)
Jacob Vlijm

Realmente fez. Implementação perfeita.
Parto

Depois de considerar, decidi seguir esta resposta por vários motivos: 1). A explicação sobre o que está acontecendo. 2) A resposta funcionar corretamente na primeira tentativa
Parto

11

Código-golfe 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 apenas alguns kilobytes), você pode gerar a lista de arquivos com um comando diferente e canalizar para prename, por exemplo:

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

Isso tem o benefício adicional de não tentar mover o nome do arquivo literal [[:alnum:]]?*se nenhum arquivo corresponder ao padrão global. findtambém permite muito mais critérios de correspondência do que o shell globbing. Uma alternativa é definir a nullglobopção de shell e fechar o fluxo de entrada padrão de prename. 1 1

Nos dois casos, remova a -nopção 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 1 shopt -s nullglob; prename ... <&-


8

Se você não se importa com o zsh, uma função e alguns 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'

A mmvfunção cria o diretório e move o arquivo. zmvdepois fornece correspondência e substituição de padrões. Primeiro, mova os nomes de arquivos começando com um alfabeto e depois todo o 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

Execute novamente sem o echona mmvdefinição do que realmente executar o movimento.


8

Não criei uma boa maneira de deixar os nomes dos diretórios em maiúsculas (ou mover os arquivos com letras maiúsculas), embora você possa fazê-lo posteriormente 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 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

Remova echoapós o teste para realmente mover os arquivos

E depois

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

remova -nse parecer bom após o teste e execute novamente.


2
Você pode usar if [[ -d "${i^}" ]]para fazer o icapital variável e mkdir {A..Z}no início.
Arronical

Deixe-me tentar isso e ver
Parto

@ Obrigado Arcronical! Vou deixá-lo no entanto, desde que você postou seu próprio caminho
Zanna

@ Zanna Eu gosto que chegamos a ele de diferentes direções, percorrendo as letras e usando-as como critérios de pesquisa que nunca haviam me ocorrido. Tenho certeza de que há uma solução inteligente e rápida com o find, mas não consigo entender isso!
Arronical

Hey Zanna, isso não moveu as fontes que começam com uma letra maiúscula. Caso contrário, funcionou bem.
Parto

7

Os seguintes comandos no diretório que contém as fontes devem funcionar, se você desejar usar fora do diretório de armazenamento de fontes, mude 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 criará apenas 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 liner, 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âmetros bash, que será recursiva e deve ser um pouco mais rápida que a versão pura do 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 mais 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' \;

Este também funcionou. Mesmo para letras maiúsculas e minúsculas.
Parto

5

Mapeie cada nome de arquivo para um nome de diretório usando tr, then 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'

Eu realmente gosto disso, é o que eu estava procurando com o meu desejo de usar o find. Existe uma maneira de criar apenas nomes de diretório em maiúsculas e mover nomes de arquivos em minúsculas para eles?
Arronical

11
Eu adicionei outro trpara converter em maiúsculas.
xn.

Por que o desvio xargsapenas para ligar bashnovamente? Não seria mais simples e muito mais legível canalizar a saída findpara um loop while e readgravar registro por registro?
David Foerster

Boa pergunta, @DavidFoerster. Eu acho que viés contra loops em one-liners e preferência por uma abordagem mais funcional. Mas um script bash em uma string também não é muito elegante, então eu diria que a whileversão do loop ( bit.ly/2j2mhyb ) talvez seja melhor.
xn.

By the way, você pode evitar a desagradável {}substituição se você deixar xargsde acrescentar o argumento e, em seguida, referem-se $1dentro do script shell, por exemplo: xargs -0 -n1 -- bash -c 'dir=/dest/$(basename "$1" | ...); ...; mv "$1" "$dir"' _. (Mente a final _!)
David Foerster
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.