Estou procurando uma maneira de listar todos os arquivos em um diretório que contenham o conjunto completo de palavras-chave que estou procurando, em qualquer lugar do arquivo.
Portanto, as palavras-chave não precisam aparecer na mesma linha.
Uma maneira de fazer isso seria:
grep -l one $(grep -l two $(grep -l three *))
Três palavras-chave são apenas um exemplo, podem ser duas ou quatro e assim por diante.
Uma segunda maneira em que posso pensar é:
grep -l one * | xargs grep -l two | xargs grep -l three
Um terceiro método, que apareceu em outra pergunta , seria:
find . -type f \
-exec grep -q one {} \; -a \
-exec grep -q two {} \; -a \
-exec grep -q three {} \; -a -print
Mas essa definitivamente não é a direção que eu vou aqui. Eu quero algo que exige menos digitação, e possivelmente apenas uma chamada para grep
, awk
, perl
ou similar.
Por exemplo, eu gosto de como awk
permite combinar linhas que contêm todas as palavras-chave , como:
awk '/one/ && /two/ && /three/' *
Ou imprima apenas os nomes dos arquivos:
awk '/one/ && /two/ && /three/ { print FILENAME ; nextfile }' *
Mas quero encontrar arquivos em que as palavras-chave possam estar em qualquer lugar do arquivo, não necessariamente na mesma linha.
As soluções preferidas seriam compatíveis com o gzip, por exemplo, grep
tem a zgrep
variante que funciona em arquivos compactados. Por que mencionei isso, é que algumas soluções podem não funcionar bem, devido a essa restrição. Por exemplo, no awk
exemplo de impressão de arquivos correspondentes, você não pode simplesmente fazer:
zcat * | awk '/pattern/ {print FILENAME; nextfile}'
Você precisa alterar significativamente o comando, para algo como:
for f in *; do zcat $f | awk -v F=$f '/pattern/ { print F; nextfile }'; done
Portanto, devido à restrição, é necessário ligar awk
várias vezes, mesmo que você possa fazê-lo apenas uma vez com arquivos não compactados. E certamente, seria melhor fazer zawk '/pattern/ {print FILENAME; nextfile}' *
e obter o mesmo efeito, então eu preferiria soluções que permitam isso.
gzip
amigáveis, apenaszcat
os arquivos primeiro.grep
soluções são facilmente adaptáveis apenas prefixando asgrep
chamadas com az
, não há necessidade de eu também lidar com nomes de arquivos.grep
. AFAIK, apenasgrep
ecat
tem "variantes z" padrão. Acho que você não conseguirá nada mais simples do que usar umafor f in *; do zcat -f $f ...
solução. Qualquer outra coisa teria que ser um programa completo que verifique os formatos de arquivo antes de abrir ou use uma biblioteca para fazer o mesmo.Respostas:
Se você quiser manipular automaticamente arquivos compactados com gzip, execute-o em um loop com
zcat
(lento e ineficiente, porque você estará bifurcandoawk
várias vezes em um loop, uma vez para cada nome de arquivo) ou reescreva o mesmo algoritmoperl
e use oIO::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 usadaIO::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 oGetopt::Std
ouGetopt::Long
os módulos.Execute-o assim:
(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)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) emap()
é 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);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 comfind -exec
ouxargs
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 aslibperlio-gzip-perl
) em vez deIO::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 deSet::Scalar
(o que também economizou alguns segundos com aIO::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 googleperl 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 umxargs -P
invólucro (ou 1m13s semxargs -P
).fonte
for f in *; do zcat $f | awk -v F=$f '/one/ {a++}; /two/ {b++}; /three/ {c++}; a&&b&&c { print F; nextfile }'; done
funciona bem, mas na verdade leva três vezes mais tempo que a minhagrep
solução e é realmente mais complicado.apt-get install libset-scalar-perl
usar o script. Mas parece não terminar em um tempo razoável.Defina o separador de registros como
.
paraawk
tratar o arquivo inteiro como uma linha:Da mesma forma com
perl
:fonte
for f in *; do zcat $f | awk -v RS='.' -v F=$f '/one/ && /two/ && /three/ { print F }'; done
não produz nada.zcat -f "$f"
se alguns dos arquivos não estiverem compactados.awk -v RS='.' '/bfs/&&/none/&&/rgg/{print FILENAME}' greptest/*.txt
ainda não retorna resultados, enquantogrep -l rgg $(grep -l none $(grep -l bfs greptest/*.txt))
retorna os resultados esperados.Para arquivos compactados, você pode fazer um loop sobre cada arquivo e descomprimir primeiro. Em seguida, com uma versão ligeiramente modificada das outras respostas, você pode:
O script Perl sairá com
0
status (sucesso) se todas as três cadeias forem encontradas. O}{
é Perl, abreviação deEND{}
. Qualquer coisa a seguir será executada após todas as entradas terem sido processadas. Portanto, o script sairá com um status de saída diferente de 0 se nem todas as seqüências de caracteres foram encontradas. Portanto,&& printf '%s\n' "$f"
ele imprimirá o nome do arquivo apenas se todos os três forem encontrados.Ou, para evitar carregar o arquivo na memória:
Por fim, se você realmente deseja fazer a coisa toda em um script, pode:
Salve o script acima como
foo.pl
em algum lugar no seu$PATH
, torne-o executável e execute-o assim:fonte
De todas as soluções propostas até agora, minha solução original usando grep é a mais rápida, terminando em 25 segundos. A desvantagem é que é entediante adicionar e remover palavras-chave. Então, eu vim com um script (apelidado
multi
) que simula o comportamento, mas permite alterar a sintaxe:Então, agora, escrever
multi grep one two three -- *
é equivalente à minha proposta original e é executado ao mesmo tempo. Também posso usá-lo facilmente em arquivos compactados usandozgrep
o primeiro argumento.Outras soluções
Também experimentei um script Python usando duas estratégias: pesquisar todas as palavras-chave linha por linha e pesquisar no arquivo inteiro palavra-chave por palavra-chave. A segunda estratégia foi mais rápida no meu caso. Mas foi mais lento do que apenas usar
grep
, terminando em 33 segundos. A correspondência de palavras-chave linha por linha terminou em 60 segundos.O script dado por terdon terminou em 54 segundos. Na verdade, demorou 39 segundos em tempo de espera, porque meu processador é dual core. O que é interessante, porque meu script Python levou 49 segundos de tempo de exibição (e
grep
tinha 29 segundos).O script cas não conseguiu terminar em um tempo razoável, mesmo em um número menor de arquivos que foram processados em
grep
menos de 4 segundos, então tive que eliminá-lo.Mas sua
awk
proposta original , embora seja mais lenta dogrep
que é, tem uma vantagem potencial. Em alguns casos, pelo menos na minha experiência, é possível esperar que todas as palavras-chave apareçam em algum lugar do cabeçalho do arquivo, caso estejam no arquivo. Isso dá a esta solução um aumento drástico no desempenho:Termina em um quarto de segundo, em oposição a 25 segundos.
Obviamente, talvez não tenhamos a vantagem de procurar por palavras-chave conhecidas por ocorrerem no início dos arquivos. Nesse caso, a solução sem
NR>100 {exit}
demora 63 segundos (50s de tempo na parede).Arquivos não compactados
Não há diferença significativa no tempo de execução entre minha
grep
solução e aawk
proposta do caso, ambas levam uma fração de segundo para serem executadas.Observe que a inicialização da variável
FNR == 1 { f1=f2=f3=0; }
é obrigatória nesse caso para redefinir os contadores para cada arquivo processado subsequente. Como tal, esta solução requer a edição do comando em três locais, se você desejar alterar uma palavra-chave ou adicionar novas. Por outro lado,grep
você pode simplesmente acrescentar| xargs grep -l four
ou editar a palavra-chave desejada.Uma desvantagem da
grep
solução que usa substituição de comando é que ela travará se em qualquer lugar da cadeia, antes da última etapa, não houver arquivos correspondentes. Isso não afeta axargs
variante porque o tubo será abortado uma vez quegrep
retorne um status diferente de zero. Atualizei meu script para uso,xargs
para que eu não precise lidar com isso sozinho, tornando o script mais simples.fonte
not all(p in text for p in patterns)
not
) e terminei em 32 segundos, então não há muita melhoria, mas é certamente mais legível.PerlIO::gzip
vez deIO::Uncompress::AnyUncompress
. agora leva apenas 3,1 segundos em vez de 1m13s para processar meus 74MB de arquivos de log.eval $(lesspipe)
(por exemplo, no seu.profile
, etc), você pode usar emless
vez dezcat -f
e seufor
wrapper de loopawk
poderá processar qualquer tipo de arquivo queless
possa (gzip, bzip2, xz e mais) .... less pode detectar se stdout é um pipe e apenas enviará um fluxo para stdout, se for.Outra opção - alimente as palavras uma de cada vez para
xargs
que elas sejam executadasgrep
no arquivo.xargs
ele próprio pode sair assim que uma invocação degrep
retornos falhar retornando255
a ele (consulte axargs
documentação). É claro que a criação de conchas e bifurcações envolvidas nesta solução provavelmente diminuirá significativamentee para enrolá-lo
fonte
_
efile
? Essa pesquisa em vários arquivos passou como argumento e retornará arquivos que contêm todas as palavras-chave?_
isso, está sendo passado como o$0
para o shell gerado - isso apareceria como o nome do comando na saída deps
- eu adiaria para o mestre aqui