awk 'FNR == 1 { f1=f2=f3=0; };
/one/ { f1++ };
/two/ { f2++ };
/three/ { f3++ };
f1 && f2 && f3 {
print FILENAME;
nextfile;
}' *
Se você quiser manipular automaticamente arquivos compactados com gzip, execute-o em um loop com zcat
(lento e ineficiente, porque você estará bifurcando awk
várias vezes em um loop, uma vez para cada nome de arquivo) ou reescreva o mesmo algoritmo perl
e use o IO::Uncompress::AnyUncompress
módulo de biblioteca que pode descompacte vários tipos diferentes de arquivos compactados (gzip, zip, bzip2, lzop). ou em python, que também possui módulos para manipular arquivos compactados.
Aqui está uma perl
versão usada IO::Uncompress::AnyUncompress
para permitir qualquer número de padrões e nomes de arquivos (contendo texto sem formatação ou texto compactado).
Todos os argumentos anteriores --
são tratados como padrões de pesquisa. Todos os argumentos posteriores --
são tratados como nomes de arquivos. Manuseio de opções primitivo, mas eficaz para este trabalho. Melhor manipulação opção (por exemplo, para suportar uma -i
opção para pesquisas maiúsculas e minúsculas) poderia ser alcançado com o Getopt::Std
ou Getopt::Long
os módulos.
Execute-o assim:
$ ./arekolek.pl one two three -- *.gz *.txt
1.txt.gz
4.txt.gz
5.txt.gz
1.txt
4.txt
5.txt
(Não listarei arquivos {1..6}.txt.gz
e {1..6}.txt
aqui ... eles contêm apenas algumas ou todas as palavras "um" "dois" "três" "quatro" "cinco" e "seis" para teste. Os arquivos listados na saída acima Contenha todos os três padrões de pesquisa. Teste você mesmo com seus próprios dados)
#! /usr/bin/perl
use strict;
use warnings;
use IO::Uncompress::AnyUncompress qw(anyuncompress $AnyUncompressError) ;
my %patterns=();
my @filenames=();
my $fileargs=0;
# all args before '--' are search patterns, all args after '--' are
# filenames
foreach (@ARGV) {
if ($_ eq '--') { $fileargs++ ; next };
if ($fileargs) {
push @filenames, $_;
} else {
$patterns{$_}=1;
};
};
my $pattern=join('|',keys %patterns);
$pattern=qr($pattern);
my $p_string=join('',sort keys %patterns);
foreach my $f (@filenames) {
#my $lc=0;
my %s = ();
my $z = new IO::Uncompress::AnyUncompress($f)
or die "IO::Uncompress::AnyUncompress failed: $AnyUncompressError\n";
while ($_ = $z->getline) {
#last if ($lc++ > 100);
my @matches=( m/($pattern)/og);
next unless (@matches);
map { $s{$_}=1 } @matches;
my $m_string=join('',sort keys %s);
if ($m_string eq $p_string) {
print "$f\n" ;
last;
}
}
}
Um hash %patterns
é contém o conjunto completo de padrões que os arquivos devem conter pelo menos um de cada membro
$_pstring
é uma sequência que contém as chaves classificadas desse hash. A sequência $pattern
contém uma expressão regular pré-compilada também criada a partir do %patterns
hash.
$pattern
é comparado com cada linha de cada arquivo de entrada (usando o /o
modificador para compilar $pattern
apenas uma vez, como sabemos que nunca será alterado durante a execução) e map()
é usado para criar um hash (% s) contendo as correspondências para cada arquivo.
Sempre que todos os padrões tiverem sido vistos no arquivo atual (comparando se $m_string
(as chaves classificadas %s
) são iguais a $p_string
), imprima o nome do arquivo e pule para o próximo arquivo.
Esta não é uma solução particularmente rápida, mas não é excessivamente lenta. A primeira versão levou 4m58s para procurar três palavras em arquivos de log compactados no valor de 74 MB (totalizando 937 MB descompactados). Esta versão atual leva 1m13s. Provavelmente existem outras otimizações que poderiam ser feitas.
Uma otimização óbvia é usar isso em conjunto com xargs
o -P
aka --max-procs
para executar várias pesquisas em subconjuntos dos arquivos em paralelo. Para fazer isso, você precisa contar o número de arquivos e dividir pelo número de núcleos / cpus / threads que seu sistema possui (e arredondar para cima adicionando 1). por exemplo, havia 269 arquivos sendo pesquisados no meu conjunto de amostras e meu sistema possui 6 núcleos (um AMD 1090T);
patterns=(one two three)
searchpath='/var/log/apache2/'
cores=6
filecount=$(find "$searchpath" -type f -name 'access.*' | wc -l)
filespercore=$((filecount / cores + 1))
find "$searchpath" -type f -print0 |
xargs -0r -n "$filespercore" -P "$cores" ./arekolek.pl "${patterns[@]}" --
Com essa otimização, foram necessários apenas 23 segundos para encontrar todos os 18 arquivos correspondentes. Obviamente, o mesmo poderia ser feito com qualquer uma das outras soluções. NOTA: A ordem dos nomes de arquivos listados na saída será diferente; portanto, talvez seja necessário classificá-los posteriormente, se isso for importante.
Conforme observado por @arekolek, vários zgrep
s com find -exec
ou xargs
podem fazê-lo significativamente mais rápido, mas esse script tem a vantagem de oferecer suporte a qualquer número de padrões a serem pesquisados e é capaz de lidar com vários tipos diferentes de compactação.
Se o script estiver limitado a examinar apenas as primeiras 100 linhas de cada arquivo, ele será executado em todas elas (no meu exemplo de 74MB de 269 arquivos) em 0,6 segundos. Se isso for útil em alguns casos, poderá ser transformado em uma opção de linha de comando (por exemplo -l 100
), mas corre o risco de não encontrar todos os arquivos correspondentes.
BTW, de acordo com a página do manual IO::Uncompress::AnyUncompress
, os formatos de compactação suportados são:
Uma última (espero) otimização. Ao usar o PerlIO::gzip
módulo (empacotado no debian as libperlio-gzip-perl
) em vez de IO::Uncompress::AnyUncompress
reduzir o tempo para cerca de 3,1 segundos para processar meus 74 MB de arquivos de log. Houve também algumas pequenas melhorias usando um hash simples em vez de Set::Scalar
(o que também economizou alguns segundos com a IO::Uncompress::AnyUncompress
versão).
PerlIO::gzip
foi recomendado como o gunzip perl mais rápido em /programming//a/1539271/137158 (encontrado em uma pesquisa no google perl fast gzip decompress
)
Usar xargs -P
com isso não melhorou nada. De fato, parecia até abrandar em 0,1 a 0,7 segundos. (Tentei quatro execuções e meu sistema faz outras coisas em segundo plano, o que altera o tempo)
O preço é que esta versão do script pode manipular apenas arquivos compactados e compactados com gzip. Velocidade vs flexibilidade: 3,1 segundos para esta versão vs 23 segundos para a IO::Uncompress::AnyUncompress
versão com um xargs -P
invólucro (ou 1m13s sem xargs -P
).
#! /usr/bin/perl
use strict;
use warnings;
use PerlIO::gzip;
my %patterns=();
my @filenames=();
my $fileargs=0;
# all args before '--' are search patterns, all args after '--' are
# filenames
foreach (@ARGV) {
if ($_ eq '--') { $fileargs++ ; next };
if ($fileargs) {
push @filenames, $_;
} else {
$patterns{$_}=1;
};
};
my $pattern=join('|',keys %patterns);
$pattern=qr($pattern);
my $p_string=join('',sort keys %patterns);
foreach my $f (@filenames) {
open(F, "<:gzip(autopop)", $f) or die "couldn't open $f: $!\n";
#my $lc=0;
my %s = ();
while (<F>) {
#last if ($lc++ > 100);
my @matches=(m/($pattern)/ogi);
next unless (@matches);
map { $s{$_}=1 } @matches;
my $m_string=join('',sort keys %s);
if ($m_string eq $p_string) {
print "$f\n" ;
close(F);
last;
}
}
}
gzip
amigáveis, apenaszcat
os arquivos primeiro.