Como mover todos os arquivos que correspondem a um determinado nome para uma nova pasta se o número de arquivos correspondentes for maior que 10?

2

Estou procurando criar um script que, quando executado, examine um diretório e procure por todos os arquivos e, em seguida, descubra automaticamente os padrões de nome de arquivo para movê-los com base na lógica adicional indicada abaixo.

Digamos que eu tenha os seguintes arquivos em uma pasta:

  • aaa.txt
  • temp-203981.log
  • temp-098723.log
  • temp-123197.log
  • temp-734692.log
  • test1.sh
  • test2.sh
  • test3.sh

O script deve poder pesquisar automaticamente o diretório e descobrir que há 4 arquivos (temp-XXX.log) e 3 arquivos (testXXX.sh) que possuem um prefixo correspondente em seu nome. Então, depois de ter encontrado o número de arquivos, deve compará-lo a um limite definido, digamos 3.

Se o número de arquivos que correspondem ao nome especificado for maior que o limite, ele deverá mover os arquivos encontrados para uma pasta com o nome da parte dos nomes de arquivos correspondentes.

Assim, a pasta principal acima deve ser semelhante a:

  • aaa.txt
  • temp.log (Essa seria a pasta que contém temp-734692.log, temp-123197.log, temp-098723.log, temp-203981.log)
  • test.sh (Essa seria a pasta que contém test1.sh, test2.sh, test3.sh)

Espero que isso faça sentido.

P.S. Eu estou usando o ASH para este script, então ele precisará ser capaz de rodar sem muitas das habilidades extravagantes do bash, caso contrário isso seria mais fácil.

Obrigado!

EDIT: A clareza muda no começo. Além disso, pode ser mais fácil se eu fornecer um delimitador predeterminado, digamos "&", que todos os nomes de arquivo terão. O script ainda precisará criar nomes de pastas variáveis com base nos nomes dos arquivos antes do delimitador, mas acho que isso tornará as coisas mais claras e fáceis.

    
por lukemk1 15.11.2017 / 21:12

3 respostas

1

Verifique, funciona e adicionarei explicação, como funciona. Eu testei no dash .

Observação: os nomes dos arquivos não devem conter espaços, novas linhas.

#!/bin/dash

limit=1

printf "%s\n" * |
sed 's/[-0-9]*\..*$//' |
uniq -c |
awk -v lim=${limit} '$1 >= lim {print $2}' |
sort -r |
while read -r i; do
    for j in "${i}"*; do
        [ -f "$j" ] || continue

        dir=${i}.${j#*.}

        [ -d "$dir" ] || mkdir "$dir"
        mv -v "$j" "$dir"
    done
done

Há um problema aqui - o caso, quando o nome do arquivo é igual ao nome do diretório futuro, como aaa.txt . No caso aaa.txt , o nome do arquivo não possui nenhum caractere extra, portanto, nada será removido dele, portanto, o novo nome do diretório será o mesmo, causando erro:

mkdir: cannot create directory ‘aaa.txt’: File exists
mv: 'aaa.txt' and 'aaa.txt' are the same file

Uma solução deste problema é a verificação, o suposto nome do diretório é igual ao nome do arquivo e, em seguida, adicionar algum número ao nome do diretório futuro, como aaa1.txt .

Demonstração

Antes da execução do script.

$ tree
.
├── aaa.txt
├── temp-098723.log
├── temp-123197.log
├── temp-203981.log
├── temp-734692.log
├── temp-new-file123.log
├── temp-new-file-2323-12.log
├── temp-new-file-342.log
├── test1.sh
├── test2.sh
└── test3.sh

0 directories, 11 files

Após a execução do script: script.sh

$ tree
.
├── aaa.txt
├── temp.log
│   ├── temp-098723.log
│   ├── temp-123197.log
│   ├── temp-203981.log
│   └── temp-734692.log
├── temp-new-file.log
│   ├── temp-new-file123.log
│   ├── temp-new-file-2323-12.log
│   └── temp-new-file-342.log
└── test.sh
    ├── test1.sh
    ├── test2.sh
    └── test3.sh

3 directories, 11 files
    
por 16.11.2017 / 15:23
1

É possível que eu esteja entendendo mal o que você está perguntando aqui, mas como dito, acho que esta pergunta tem algumas sutilezas e requer uma solução relativamente sofisticada, ou seja, não sei quão simples um script pode ser vai fazer o que quiser. Por exemplo, vamos analisar cuidadosamente sua lista de arquivos de exemplo:

aaa.txt
temp-203981.log
temp-098723.log
temp-123197.log
temp-734692.log
test1.sh
test2.sh
test3.sh

De acordo com a sua pergunta, você deseja que os prefixos extraídos desta lista sejam temp e test , em que aaa é excluído, pois há apenas um arquivo com aaa como prefixo e seu limite de exemplo é três. Mas por que não é te um prefixo, pois há 7 arquivos que começam com te ? Ou, como parece que você deseja primeiro agrupar os arquivos com base nos sufixos de nome de arquivo, por que um dos novos subdiretórios não é t.log ou temp-.log em vez de temp.log ? Espero que esta discussão deixe claro que, se você realmente quer que seu programa determine os prefixos potenciais por si próprio sem ter uma lista de prefixos como argumento, então há algumas ambiguidades em sua declaração de questão que precisam ser resolvidas (e algumas escolhas correspondentes). que precisam ser feitas).

Aqui está um script Python que usa uma estrutura de dados simples trie para pesquisar os prefixos de correspondência mais longos que satisfazem algumas restrições (que pode ser fornecido como argumentos):

#!/usr/bin/env python2
# -*- coding: ascii -*-
"""
trieganize.py

Use the trie data structure to look for prefixes of filenames in a given
directory and then reorganiz those files into subdirectories based on
those prefixes.

In this script the trie data structure is just a dictionary of the
following form:

    trie = {
        "count":    integer,
        "children": dictionary,
        "leaf":     boolean
    }

Where the dictionary keys have the following semantics.

count:
    stores the number of total descendents of the given trie node

children:
    stores the child trie nodes of the given node

leaf:
    denotes whether this trie corresponds to the final character in a word
"""

import sys
import os
import string

def add_word_to_trie(trie, word):
    """Add a new word to the trie."""
    if word:
        trie["count"] += 1
        if word[0] not in trie["children"]:
            trie["children"][word[0]] = \
                {"count": 0, "children": {}, "leaf": False}
        add_word_to_trie(trie=trie["children"][word[0]], word=word[1:])
    else:
        trie["leaf"] = True
    return(trie)

def expand_trie(trie, prefix='', words=None):
    """Given a trie, return the list of words it encodes."""
    if words is None:
        words = list()
    if trie["leaf"]:
        words.append(prefix)
    for character, child in trie["children"].iteritems():
        if trie["children"]:
            expand_trie(trie=child, prefix=prefix+character, words=words)
    return(words)

def extract_groups_from_trie(
    trie, threshold=0, prefix='', groups=None,
    minimum_prefix_length=0,
    maximum_prefix_length=float("inf"),
    prefix_charset=string.ascii_letters,
):
    """Given a trie and some prefix constraints, return a dictionary which
    groups together the words in the trie based on shared prefixes which
    satisfy the specified constraints.
    """
    if groups is None:
        groups = dict()
    if trie["count"] >= threshold:
        children = {
            character: child
            for character, child in trie["children"].iteritems()
            if (
                child["count"] >= threshold and
                len(prefix) + 1 >= minimum_prefix_length and
                len(prefix) + 1 <= maximum_prefix_length and
                character in prefix_charset
            )
        }
        if not children:
            groups[prefix] = expand_trie(trie, prefix)
        else:
            for character, child in children.iteritems():
                extract_groups_from_trie(
                    trie=child, threshold=threshold,
                    prefix=prefix+character, groups=groups
                )
    return(groups)

def reorganize_files(basedir, suffix_separator='.', threshold=3):
    """Takes a path to a directory and reorganizes the files in that
    directory into subdirectories based on the prefixes of their
    filenames."""

    # Get the list of file names
    filenames = os.listdir(basedir)

    # Group the filenames by suffix
    suffixes = {}
    for filename in filenames:
        basename, separator, suffix = filename.rpartition(suffix_separator)
        if suffix not in suffixes:
            suffixes[suffix] = []
        suffixes[suffix].append(basename)

    # For each suffix, search for prefixes
    for suffix, basenames in suffixes.iteritems():

        # Initialize a trie object
        trie = {"count":0, "children": {}, "leaf": False}

        # Add the filenames to the trie
        for basename in basenames:
            add_word_to_trie(trie, basename)

        # Break the filenames up into groups based on their prefixes
        groups = extract_groups_from_trie(trie, threshold)

        # Organize the groups of files into subdirectories
        for prefix, group in groups.iteritems():
            targetdir = os.path.join(basedir, prefix + suffix_separator + suffix)
            os.mkdir(targetdir)
            for basename in group:
                filename = basename + suffix_separator + suffix
                sourcefile = os.path.join(basedir, filename) 
                targetfile = os.path.join(targetdir, filename)
                os.rename(sourcefile, targetfile)

if __name__=="__main__":
    reorganize_files(basedir=sys.argv[1])

Para demonstrar este script Python, escrevi um pequeno script de shell para criar e preencher um diretório de teste:

#!/usr/bin/bash

# create-test-dir.sh

rm -rf /tmp/testdir
mkdir -p /tmp/testdir

files=(
aaa.txt
temp-203981.log
temp-098723.log
temp-123197.log
temp-734692.log
test1.sh
test2.sh
test3.sh
)

for file in ${files[@]}; do touch "/tmp/testdir/${file}"; done

Podemos executar o script:

bash create-test-dir.sh

Em seguida, nosso diretório de teste é semelhante a este (executar tree /tmp/testdir ):

/tmp/testdir/
|-- aaa.txt
|-- temp-098723.log
|-- temp-123197.log
|-- temp-203981.log
|-- temp-734692.log
|-- test1.sh
|-- test2.sh
'-- test3.sh

0 directories, 8 files

Agora podemos executar o script Python:

python trieganize.py /tmp/testdir

E depois os arquivos são organizados da seguinte forma:

/tmp/testdir/
|-- aaa.txt
|-- temp.log
|   |-- temp-098723.log
|   |-- temp-123197.log
|   |-- temp-203981.log
|   '-- temp-734692.log
'-- test.sh
    |-- test1.sh
    |-- test2.sh
    '-- test3.sh

2 directories, 8 files
    
por 16.11.2017 / 07:55
0

Sim, bash facilitaria isso, mas aqui está uma solução POSIX:

#!/bin/sh

for pattern in "$@"; do
   set -- "$pattern"*

   if [ $# -gt 2 ]; then

      for f in "$@"; do
         [ -f "$f" ] || continue
         ext="${f##*.}"
         dest="${pattern}.${ext}"
         [ -d "$dest" ] || mkdir "$dest"
         mv "$f" "$dest"
      done

   fi
done

exit

Isso leva qualquer número de padrões, por exemplo %código%. Para cada padrão, defina os parâmetros posicionais para os arquivos correspondentes ao padrão e mova-os para uma pasta chamada ./script temp test se houver 3 ou mais arquivos correspondentes ao padrão. Eu usei seus arquivos de exemplo e obtive os resultados pretendidos.

EDIT: teste que <pattern>.<file_extension> é um arquivo regular para evitar mover diretórios e tal.

    
por 15.11.2017 / 23:07