Faça o AWK processar um enorme conjunto de arquivos com um único processo

1

Eu preciso processar um grande subconjunto de um grande conjunto de arquivos com AWK (*) para que ele acumule um conjunto de variáveis nos arquivos.

A abordagem simples de passar vários nomes de arquivos para AWK com um curinga de arquivo funcionou bem para um pequeno conjunto de arquivos, mas resulta, provavelmente, em "Argument list too long" quando executado com um conjunto de arquivos de tamanho de produção.

Qual é a abordagem de melhores práticas para esse problema?

Alguns detalhes:

  • o conjunto inteiro de arquivos é de 20 a 50 mil arquivos; um subconjunto para uma única execução é de 5-10K por enquanto (mas ótimo se puder escalar facilmente)

  • Preciso contar ocorrências de cada palavra em um conjunto de arquivos, dando a cada arquivo um peso definido em tempo de execução: cada palavra no mesmo arquivo recebe o mesmo peso, mas a mesma palavra que ocorre em arquivos diferentes é diferente peso. Para cada palavra, os pesos dos arquivos são adicionados.

  • Portanto, dividir o conjunto de arquivos em subconjuntos menores significaria agregar resultados intermediários. Ele não parece muito elegante e exigirá a adição de pontos flutuantes ao unir vários arquivos intermediários, o que torna todo o procedimento ainda menos legível e intuitivo.

  • outra abordagem em que posso pensar é alimentar awk com uma saída de find & %código%. O que eu não gosto é sacrificar a legibilidade do cat / BEGINFILE e contornar a análise de algum delimitador entre os arquivos para redefinir o peso específico do arquivo, contadores e matrizes.

  • O subconjunto de arquivos
  • para processar a partir da pasta atual é fornecido como um arquivo A separado; na seção ENDFILE eu pulo arquivos que não preciso

  • peso para cada arquivo X é derivado de uma combinação desse arquivo com um arquivo de referência B; Basicamente, é uma proporção de palavras comuns entre X e B para o número de palavras em X
  • separar o cálculo do peso do arquivo da agregação entre os arquivos significaria que dois readpassam dezenas de GB, o que eu gostaria de evitar

(*) Ou talvez BEGINFILE não seja a melhor ferramenta para esse processamento? Se sim, que alternativa você recomendaria?

    
por wass rubleff 23.08.2018 / 01:28

2 respostas

0

Uma opção, se os nomes dos seus arquivos não contiverem citações ou espaços em branco, seria juntá-los com cat :

printf '%s ' * | xargs cat | awk ...

O texto acima simplesmente contorna o erro "lista de argumentos muito longa" usando um builtin ( printf ) para imprimir cada nome de arquivo, que é enviado para xargs , que divide os nomes dos arquivos em lotes que envia para cat , cuja saída é então enviada para awk .

Mas: não use xargs

Se você tem o GNU awk disponível (gawk) na versão 4.1 ou superior , onde o carregamento do módulo dinâmico foi introduzido, ele contém uma extensão que pode ler um diretório em si, evitando o problema.

Aqui está um exemplo de programa gawk que irá abrir e ler os arquivos em qualquer diretório que você passar para ele; você então terá que ler explicitamente de cada arquivo que você está interessado. O benefício é que você tem um único programa (GNU) awk que lerá todos os arquivos.

@load "readdir"
@load "filefuncs"

BEGIN { FS = "/" }
{
        result = stat($2, statdata)
        if (statdata["type"] != "file")
                next
        FS = " "
        while(getline < statdata["name"] > 0) {
                #print $1
        }
        FS = "/"
}

O loop principal do script passa por todos os argumentos dados na linha de comando e tenta abri-lo como um diretório. Os campos resultantes são:

  • $ 1 = número do inode
  • $ 2 = nome do arquivo
  • $ 3 = tipo de arquivo

Em seguida, usamos a função filefuncs stat para verificar o tipo do arquivo. Se não for um arquivo simples, nós o ignoramos. Caso contrário, definimos FS de volta para o valor normal e usamos getline para ler o arquivo. Depois que terminamos com cada arquivo, redefinimos o FS de volta para / para que ele possa dividir o próximo nome de arquivo de readdir .

Aprendi sobre o leia o documento aqui e sobre stat do arquivo do gawk aqui .

    
por 23.08.2018 / 03:24
0

Se os argumentos forem muitos, você precisará abrir e processar os arquivos por conta própria. Com o awk, sem usar nenhuma extensão, você pode usar isto (a mesma idéia que a resposta do Jeff):

awk '{ filename = $0; while(getline < filename > 0) { print $0; }}'

Por exemplo, combine com o comando find para encontrar os arquivos de que você precisa:

find /etc/ -maxdepth 1 -type f -perm -444 -size 1 | \
  awk '{ filename = $0; while(getline < filename > 0) { print filename ":" $0; }}'
Além disso, dependendo da versão do awk, é possível enviar mais arquivos para serem processados. .html # ARGC-and-ARGV "> conforme documentado aqui .

A program can alter ARGC and the elements of ARGV. Each time awk reaches the end of an input file, it uses the next element of ARGV as the name of the next input file. By storing a different string there, a program can change which files are read. Use "-" to represent the standard input. Storing additional elements and incrementing ARGC causes additional files to be read.

Para ilustrar com um exemplo:

find /etc/ -maxdepth 1 -type f -perm -444 -size 1 | \
  awk '
    # When reading from STDIN, assume it is a list of files to read
    FILENAME == "-" { ARGV[ARGC] = $0; ARGC += 1 }
    # When not reading STDIN, it is a file to process
    FILENAME != "-" { print "---", FILENAME ":" FNR ":" $0; }
    # These will run after every file, including STDIN, hence the check
    BEGINFILE { if (FILENAME != "-") { print ">>>", FILENAME; } }
    ENDFILE   { if (FILENAME != "-") { print "<<<", FILENAME, FNR, "lines"; } }'
    
por 23.08.2018 / 05:10