Movendo como arquivos nomeados para diretórios auto nomeados

4

Eu tenho vários milhares de arquivos em um diretório que gostaria de agrupar em diretórios da seguinte forma:

A partir disso:

└── Files
    ├── AAA.mkv
    ├── AAA.nfo
    ├── AAA-picture.jpg
    ├── BBB.mp4
    ├── BBB.srt
    ├── BBB-clip.mp4
    ├── CCC.avi
    ├── CCC.srt
    ├── CCC-clip.mov
    └── CCC.nfo

Para isso:

└── Files
    ├── AAA
    │   ├── AAA.mkv
    │   ├── AAA.nfo
    │   └── AAA-picture.jpg
    ├── BBB
    │   ├── BBB.mp4
    │   ├── BBB.srt
    │   └── BBB-clip.mp4
    └── CCC
         ├── CCC.avi
         ├── CCC.srt
         ├── CCC-clip.mov
         └── CCC.nfo

Os nomes dos arquivos variam em comprimento e número de palavras, às vezes separados por espaços e possivelmente alguns com hifens (além dos que terminam em '-short'. Eles são principalmente arquivos de vídeo com uma variedade de formatos / contêineres: mov / mpg / mkv / mp4 / avi / ogg.Alguns são legendados.Alguns possuem arquivos com metadados associados (.nfo ou -clip)

Edit: Os arquivos primários são vídeos (é aqui que eu gostaria de desenhar o nome do diretório). Os arquivos associados representam metadados. Alguns diferentes na nomeação apenas pela extensão. Há uma meia dúzia de outras variações no nome do arquivo base, como -clip.mp4 -clip.mov ou -picture.jpg Eu percebi que se algo fosse sugerido com aqueles poucos, então eu poderia (espero) trabalhar em descobrir o resto. Em resumo, AAA.mkv move-se para um diretório chamado AAA. Então todos os arquivos de metadados que começam com AAA se juntam a ele (por exemplo, neste exemplo: AAA-picture.jpg e AAA.nfo). Portanto, o nome da base é, na verdade, uma substring no caso do arquivo AAA-picture.jpg. Eu diria que é relativamente seguro simplesmente usar o hífen como o fator delimitador ... embora "-clip" ou "-picture" em sua totalidade seja mais seguro.

Como posso fazer isso sem ter síndrome do túnel do carpo? Eu olhei para isso mas era suficientemente diferente que minhas fracas habilidades de script fracassaram.

Obrigado.

    
por MrFinn 14.11.2016 / 07:30

3 respostas

5

Enquanto sua pergunta é marcada com bash , isso seria um pouco problemático (na minha humilde opinião) usar bash para tal tarefa. Eu sugeriria usar o python porque ele tem muitas boas funções para tarefas complexas e essa resposta fornece uma solução usando essa linguagem.

Essencialmente, o que ocorre aqui é que usamos o regex para dividir nomes de arquivos em vários delimitadores, obter apenas a primeira parte e usar conjuntos exclusivos dessas primeiras partes como nomes de base para novos diretórios.

Em seguida, percorremos novamente o diretório principal e classificamos os arquivos nos locais apropriados.

O script não faz nada de espetacular, e na análise de algoritmos isso não funcionaria muito bem, por causa dos loops aninhados, mas para a solução "rápida e suja, mas que funciona" está tudo bem. Se você está interessado no que cada linha faz, há muitos comentários adicionados para explicar a funcionalidade

Nota , a demonstração só mostra a impressão dos novos nomes de arquivos apenas para fins de teste. Descomente a parte os.rename() para mover o arquivo.

A demonstração

bash-4.3$ # Same directory structure as in OP example
bash-4.3$ ls TESTDIR
bash-4.3$ # now run script
AAA  AAA.mkv  AAA.nfo  AAA-picture.jpg  BBB  BBB-clip.mp4  BBB.mp4  BBB.srt
bash-4.3$ ./collate_files.py ./TESTDIR
/home/xieerqi/TESTDIR/AAA/AAA-picture.jpg
/home/xieerqi/TESTDIR/AAA/AAA.mkv
/home/xieerqi/TESTDIR/AAA/AAA.nfo
/home/xieerqi/TESTDIR/BBB/BBB.srt
/home/xieerqi/TESTDIR/BBB/BBB.mp4
/home/xieerqi/TESTDIR/BBB/BBB-clip.mp4

Script em si

#!/usr/bin/env python
import re,sys,os

top_dir = os.path.realpath(sys.argv[1])

# Create list of items in directory first
# splitting names at multiple separators
dir_list = [os.path.join(top_dir,re.split("[.-]",f)[0])
            for f in os.listdir(top_dir)
]
# Creating set ensures we will have unique
# directory namings
dir_set = set(dir_list)

# Make these directories first
for dir in dir_set:
    if not os.path.exists(dir):
        os.mkdir(dir)

# now get all files only, no directories
files_list = [f for f in os.listdir(top_dir)
              if os.path.isfile(os.path.join(top_dir,f))
]

# Traverse lists of directories and files,
# check if a filename starts with directory
# that we're testing now, and if it does - move
# the file to that directory
for dir in dir_set:
    id_string = os.path.basename(dir)
    for f in files_list:
        filename = os.path.basename(f)
        if filename.startswith(id_string):
           new_path = os.path.join(dir,filename)
           print(new_path)
           #os.rename(f,new_path)

Notas adicionais:

  • O script pode ser adaptado para dividir arquivos em outros separadores múltiplos (na função re.split() ): adicione entre colchetes (significando "[.-]" ) adicione os caracteres desejados.
  • A parte móvel é executada com a função os.rename() . Alternativamente, você poderia import shutil e usar a função shutil.move() . Consulte o link
por Sergiy Kolodyazhnyy 14.11.2016 / 09:20
9

Eu fiz um pequeno script bash para fazer isso, simplificado e aprimorado graças aos comentários do OP, @dannysauer, @Arronical e @Scott

#!/bin/bash
for file in *
  do mkdir -p "${file%%[.-]*}" 2>/dev/null
    if [[ -d "${file%%[.-]*}" ]]; then
       if [[ -f "$file" ]]; then
         echo mv -v -- "$file" "${file%%[.-]*}"
       fi
    fi
done

Execute com echo primeiro e, em seguida, remova echo para realmente mover os arquivos. O script deve ser executado no diretório em que você deseja mover os arquivos. Se preferir, aqui está como um comando de uma linha:

for file in *; do mkdir -p "${file%%[.-]*}"; if [[ -d "${file%%[.-]*}" ]]; then if [[ -f "$file" ]]; then echo mv -v -- "$file" "${file%%[.-]*}"; fi ; fi ; done

(novamente, remova echo após o teste)

Explicação:

  • for file in *; do mkdir -p "${file%%[.-]*}" cria um diretório com o nome da primeira parte do nome de cada arquivo (até o primeiro hífen ou caractere de ponto) O -p é muito importante aqui - sem ele, o script moverá apenas o primeiro arquivo correspondente (graças à Arronical por apontar que -p parará mkdir de tentar criar diretórios existentes e reclamar )
  • 2>/dev/null o script reclama que ele não pode criar um diretório com o mesmo nome que ele (mas ainda funciona), então descartamos o erro - isso não é necessário quando rodamos como um one-liner
  • if [[ -d "${file%%[.-]*}" ]]; then se houver um diretório com esse nome (se o mkdir for bem-sucedido), então ...
  • if [[ -f "$file" ]] se estamos lidando com um arquivo (não um diretório ou outra coisa), então ...
  • mv -v -- "$file" "${file%%[.-]*}" mova-o para o diretório correspondente.
por Zanna 14.11.2016 / 09:16
5

Em um pequeno script python:

#!/usr/bin/env python3
import shutil
import os
import sys

dr = sys.argv[1]

for f in os.listdir(dr):
    split = f.rfind("."); short = f.find("-")
    if split != -1:
        extension = f[split:]
        newname = f[:short] if short != -1 else f[:split]
        target = os.path.join(dr, newname)
        if not os.path.exists(target):
            os.mkdir(target)
        shutil.move(os.path.join(dr, f), os.path.join(target, f))

Para usá-lo:

  • copie-o para um arquivo vazio
  • Salvar como move_into.py
  • Execute-o com o diretório como argumento:

    python3 /path/to/move_into.py /path/to/directory
    

O script assume que todos os arquivos (relevantes) possuem extensões. Se um arquivo não tiver extensão, nada acontece com ele. Se isso é um problema, por favor mencione, pode ser mudado facilmente.

Explicação

  • O script procura uma extensão possível.
  • Se não estiver presente, o script deixará o arquivo (ou dir) sozinho.
  • Se o arquivo for dividido por "-", se presente, a primeira seção será usada posteriormente para criar pastas (se necessário)
  • Se não, o nome da base do arquivo é usado para nomear a pasta.

Posteriormente, o arquivo é movido para a pasta correspondente.

    
por Jacob Vlijm 14.11.2016 / 07:49