Agrupe arquivos em algumas pastas

12

Eu tenho alguns arquivos com extensões diferentes, como *.pdf, *.mp3, *.jpge alguns outros. Todos eles são armazenados em um parentdiretório.

Como posso obter uma lista de todas as extensões, criar algumas pastas com base nessas extensões e mover todos os arquivos para suas pastas relevantes?

αғsнιη
fonte

Respostas:

13

O script python abaixo faz o trabalho. Arquivos ocultos são armazenados separadamente em uma pasta, bem como arquivos sem extensão.

Como ele pode ser usado para uma variedade maior de finalidades, adicionei algumas opções:

  • Você pode definir extensões que deseja excluir da "reorganização". Se você deseja simplesmente mover tudo, definaexclude = ()
  • Você pode escolher o que fazer com pastas vazias ( remove_emptyfolders = Trueou False)
  • Caso você queira copiar os arquivos em vez de movê- los, substitua a linha:
shutil.move(subject, new_dir+"/"+name)

por:

shutil.copy(subject, new_dir+"/"+name) 

O script:

#!/usr/bin/env python3

import os
import subprocess
import shutil

# --------------------------------------------------------
reorg_dir = "/path/to/directory_to_reorganize"
exclude = (".jpg") # for example
remove_emptyfolders = True
# ---------------------------------------------------------

for root, dirs, files in os.walk(reorg_dir):
    for name in files:
        subject = root+"/"+name
        if name.startswith("."):
            extension = ".hidden_files"
        elif not "." in name:
            extension = ".without_extension"
        else:
            extension = name[name.rfind("."):]
        if not extension in exclude:
            new_dir = reorg_dir+"/"+extension[1:]
            if not os.path.exists(new_dir):
                os.mkdir(new_dir)
            shutil.move(subject, new_dir+"/"+name)

def cleanup():
    filelist = []
    for root, dirs, files in os.walk(reorg_dir):
        for name in files:
            filelist.append(root+"/"+name)
    directories = [item[0] for item in os.walk(reorg_dir)]
    for dr in directories:
        matches = [item for item in filelist if dr in item]
        if len(matches) == 0:
            try:
                shutil.rmtree(dr)
            except FileNotFoundError:
                pass

if remove_emptyfolders == True:
    cleanup()

SE houver risco de sobrescrever arquivos duplicados indesejados

À custa de algumas linhas extras, podemos evitar a substituição de possíveis duplicatas. Com o código abaixo, duplicatas serão renomeadas como:

duplicate_1_filename, duplicate_2_filename 

etc.

O script:

#!/usr/bin/env python3

import os
import subprocess
import shutil

# --------------------------------------------------------
reorg_dir = "/path/to/directory_to_reorganize"
exclude = (".jpg") # for example
remove_emptyfolders = True
# ---------------------------------------------------------

for root, dirs, files in os.walk(reorg_dir):
    for name in files:
        subject = root+"/"+name
        if name.startswith("."):
            extension = ".hidden_files"
        elif not "." in name:
            extension = ".without_extension"
        else:
            extension = name[name.rfind("."):]
        if not extension in exclude:
            new_dir = reorg_dir+"/"+extension[1:]
            if not os.path.exists(new_dir):
                os.mkdir(new_dir)
            n = 1; name_orig = name
            while os.path.exists(new_dir+"/"+name):
                name = "duplicate_"+str(n)+"_"+name_orig
                n = n+1
            newfile = new_dir+"/"+name
            shutil.move(subject, newfile)

def cleanup():
    filelist = []
    for root, dirs, files in os.walk(reorg_dir):
        for name in files:
            filelist.append(root+"/"+name)
    directories = [item[0] for item in os.walk(reorg_dir)]
    for dr in directories:
        matches = [item for item in filelist if dr in item]
        if len(matches) == 0:
            try:
                shutil.rmtree(dr)
            except FileNotFoundError:
                pass

if remove_emptyfolders == True:
    cleanup()

EDITAR

Com o OP em mente, todos esquecemos de adicionar uma instrução sobre como usar. Como perguntas duplicadas podem ( e realmente aparecem), elas podem ser úteis.

Como usar

  1. Copie qualquer um dos scripts para um arquivo vazio, salve-o como reorganize.py
  2. Na seção principal do script, defina o diretório de destino (com os arquivos a serem reorganizados):

    reorg_dir = "/path/to/directory_to_reorganize" 

    (use aspas se o diretório contiver espaços)

    possíveis extensões que você deseja excluir (provavelmente nenhuma, como abaixo):

    exclude = ()

    e se você deseja remover as pastas vazias depois:

    remove_emptyfolders = True
  3. Execute o script com o comando:

    python3 /path/to/reorganize.py

Nota: se você deseja copiar os arquivos em vez de mover , substitua:

shutil.move(subject, new_dir+"/"+name)

por:

shutil.copy(subject, new_dir+"/"+name)

Por favor, tente primeiro uma pequena amostra.

Jacob Vlijm
fonte
12

Você pode usar findcom um execcomando um pouco complexo :

find . -iname '*?.?*' -type f -exec bash -c 'EXT="${0##*.}"; mkdir -p "$PWD/${EXT}_dir"; cp --target-directory="$PWD/${EXT}_dir" "$0"' {} \;

# '*?.?*' requires at least one character before and after the '.', 
# so that files like .bashrc and blah. are avoided.
# EXT="${0##*.}" - get the extension
# mkdir -p $PWD/${EXT}_dir - make the folder, ignore if it exists

Substitua cppor echopara uma corrida a seco.


Mais eficiente e organizado seria salvar o bashcomando em um script (digamos, em /path/to/the/script.sh):

#! /bin/bash

for i
do
    EXT="${i##*.}" 
    mkdir -p "$PWD/${EXT}_dir"
    mv --target-directory="$PWD/${EXT}_dir" "$i" 
done

E então execute find:

find . -iname '*?.?*' -type f -exec /path/to/the/script.sh {} +

Essa abordagem é bastante flexível. Por exemplo, para usar o nome do arquivo em vez da extensão ( filename.ext), usamos isso para EXT:

NAME="${i##*/}"
EXT="${NAME%.*}"
muru
fonte
+1; o -iname '*.*'deveria cuidar dos casos de canto que eu estava preocupado com ... boa idéia!
Rmano
@Rmano não os *.fig.bakou .profile/.bashrc, mas deve lidar apenas com arquivos com extensões, pelo menos. Obrigado.
muru 27/08/14
6
ls | gawk -F. 'NF>1 {f= $NF "-DIR"; system("mkdir -p " f ";mv " $0 " " f)}'

Calculando a lista de extensões (depois da mudança):

ls -d *-DIR

Calculando a lista de extensões (antes de mover):

ls -X | grep -Po '(?<=\.)(\w+)$'| uniq -c | sort -n

(neste último exemplo, estamos calculando o número de arquivos para cada extensão e classificando-o)


fonte
1
pena: um erro de digitação "mkdir -f" foi corrigido para "mkdir -p" (ignorar se dir já existem)
O uniq não deve ser aplicado após a classificação? E por favor, não analise a saída de ls.
Muru
@muru, (parte 1) ls -X garante que as extensões sejam classificadas. A classificação final foi apenas para ordenar a tabela de extensões por número de ocorrências - relevância. (Estou correto?).
@muru (parte 2) ls -X | grep -Po '(?<=\.)(\w+)$'foi minha primeira ideia para obter a lista ordenada de extensões. Isso é muito ruim? O que você sugere?
Eu esqueci o que ls -Xfaz. Sobre o motivo pelo qual eu recomendo ls, consulte unix.stackexchange.com/q/128985/70524 e unix.stackexchange.com/q/112125/70524 . Para conseguir o que você faz, eu usaria um caminho mais longo: find . -type f -name '*?.?*' -print0 | sed -z 's/.*\.//' | sort -zu(com um opcional | uniq -cz, se forem necessárias contagens). E find ... -print0 | gawk -v RS='\0'(mesmo que isso não seja muito portátil ) para o primeiro.
muru 31/12/14
5

Experimente este script de shell.

#!/bin/sh
src=`dirname "$1"`/`basename "$1"`;
for file in "$src"/*?.?*; do
  if test -f "$file"; then
    dest="$src${file##*.}"_files;
    mkdir -p "$dest";
    mv "$file" "$dest";
  fi;
done;

# pass the directory to re-organize as first argument
# moves only regular files which have extension
# ignores other type of files including
# files having no extension, hidden files, directories, and links.
Prashant Karmakar
fonte
1
Sinto muito, isso é um erro. Eu deveria ter substituído todas as ocorrências de filepathwith file. Eu vou corrigir isso diretamente.
Prashant Karmakar
Por favor, não analise a saída de ls. Em vez disso, façafor file in "$src"/*?.?*; do ..
muru
@muru isso funcionará corretamente se o nome de algum arquivo tiver espaços?
Prashant Karmakar
@PrashantKarmakar sim, considerando que readpode ter um comportamento inesperado. Você também deve citar as variáveis ​​nos comandos mkdir e mv.
muru 31/12/14
Teste, se você quiser:for i in *; do printf "%s\n" "$i"; done; for i in $(ls -d); do printf "%s\n" "$i"; done
muru 31/12/14
2

Se você tiver renomeado / nome de usuário do Perl instalado:

rename 's!(.*)\.(\w+)$! mkdir($2); "$2/$&"!ge'  *
muru
fonte