grep para várias strings em arquivo em linhas diferentes (ou seja, arquivo inteiro, não pesquisa baseada em linha)?


85

Eu quero procurar arquivos contendo as palavras Dansk, Svenskaou Norskem qualquer linha, com um código de retorno utilizável (como eu realmente só gosto de ter a informação de que as strings estão contidas, meu one-liner vai um pouco além disso).

Tenho muitos arquivos com linhas como este:

Disc Title: unknown
Title: 01, Length: 01:33:37.000 Chapters: 33, Cells: 31, Audio streams: 04, Subpictures: 20
        Subtitle: 01, Language: ar - Arabic, Content: Undefined, Stream id: 0x20, 
        Subtitle: 02, Language: bg - Bulgarian, Content: Undefined, Stream id: 0x21, 
        Subtitle: 03, Language: cs - Czech, Content: Undefined, Stream id: 0x22, 
        Subtitle: 04, Language: da - Dansk, Content: Undefined, Stream id: 0x23, 
        Subtitle: 05, Language: de - Deutsch, Content: Undefined, Stream id: 0x24, 
(...)

Aqui está o pseudocódigo do que eu quero:

for all files in directory;
 if file contains "Dansk" AND "Norsk" AND "Svenska" then
 then echo the filename
end

Qual é a melhor maneira de fazer isso? Isso pode ser feito em uma linha?

Respostas:


89

Você pode usar:

grep -l Dansk * | xargs grep -l Norsk | xargs grep -l Svenska

Se você também deseja encontrar arquivos ocultos:

grep -l Dansk .* | xargs grep -l Norsk | xargs grep -l Svenska

Solução inteligente; uma coisa a observar (em geral; não relevante para o que o OP estava pedindo) é que o código de saída geral será 0, mesmo em caso de falha (conceitual). Portanto, se você estiver interessado em determinar o fracasso versus o sucesso, terá que examinar se a saída stdout está vazia ou não ou, em vez disso, empregar a abordagem de @EddSteel.
mklement0 de

@mklement: No Bash, a PIPESTATUSmatriz contém os valores de saída dos membros de um pipeline.
Pausado até novo aviso.

@DennisWilliamson É bom saber, obrigado. Outra opção é ativar a pipefailopção shell (temporariamente):shopt -so pipefail
mklement0

4
Você pode querer usar grep -Ze xargs -0se seus nomes de arquivo podem conter espaços.
Ben Challenor

1
Isso pode causar erros de "Lista de argumentos muito longa" se você tiver muitos arquivos.
AnnanFay de

23

Ainda outra maneira usando apenas bash e grep:

Para um único arquivo 'test.txt':

  grep -q Dansk test.txt && grep -q Norsk test.txt && grep -l Svenska test.txt

Será impresso test.txtse o arquivo contiver todos os três (em qualquer combinação). Os dois primeiros greps não imprimem nada ( -q) e o último só imprime o arquivo se os outros dois tiverem passado.

Se você quiser fazer isso para cada arquivo no diretório:

   para f em *; fazer grep -q Dansk $ f && grep -q Norsk $ f && grep -l Svenska $ f; feito

mas então não há necessidade de executar grep 3 vezes.
kurumi

1
Eu sei que você pode combinar padrões com -e, mas não consigo ver uma maneira de fazer uma conjunção apenas no grep.
Edd Steel

1
Ótimo; re for f ...: use "$f"(aspas duplas) em vez de apenas $fgarantir que os nomes dos arquivos com espaços embutidos, etc. sejam tratados corretamente.
mklement0 de

A vantagem dessa abordagem em relação à de @ vmpstr é que o código de saída reflete corretamente se todos os termos de pesquisa foram encontrados ou não.
mklement0 de

19
grep –irl word1 * | grep –il word2 `cat -` | grep –il word3 `cat -`
  • -i torna a pesquisa insensível a maiúsculas e minúsculas
  • -r torna a pesquisa de arquivos recursiva através de pastas
  • -l canaliza a lista de arquivos com a palavra encontrada
  • cat - faz com que o próximo grep examine os arquivos passados ​​para sua lista.

1
esta é a resposta mais simples e direta, muito obrigado!
majick

9

Como fazer o grep para várias strings no arquivo em linhas diferentes (use o símbolo de barra vertical):

for file in *;do 
   test $(grep -E 'Dansk|Norsk|Svenska' $file | wc -l) -ge 3 && echo $file
done

Notas:

  1. Se você usar aspas duplas ""com seu grep, terá que escapar do tubo assim: \|para pesquisar Dansk, Norsk e Svenska.

  2. Presume que uma linha possui apenas um idioma.

Passo a passo: http://www.cyberciti.biz/faq/howto-use-grep-command-in-linux-unix/


Isso não falharia se Dansk Norsk e Svenska aparecessem na mesma linha?
vmpstr

Sim. Falharia nesse caso. Presumi que os idiomas aparecem um por linha.
Damodharan R

Também seria arquivado se eu tivesse Norsk, mas em três linhas diferentes.
Benjamin W.

6

Você pode fazer isso facilmente com ack :

ack -l 'cats' | ack -xl 'dogs'
  • -l: retorna uma lista de arquivos
  • -x: pegue os arquivos de STDIN (a pesquisa anterior) e pesquise apenas esses arquivos

E você pode simplesmente continuar enviando até obter os arquivos que deseja.


Quando tento fazer isso, diz Unknown option: x. Existe uma certa versão do ack que suporta este sinalizador x?
Hassan de

4
awk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print "0" }' 

você pode então pegar o valor de retorno com o shell

se você tem Ruby (1.9+)

ruby -0777 -ne 'print if /Dansk/ and /Norsk/ and /Svenka/' file

1
em sua cláusula awk END, você provavelmente quer if (a && b && c) {exit 0} else {exit 1}:, ou mais concisoexit !(a && b && c)
glenn jackman

sua solução rubi não parece certa. isso só imprimirá parágrafos que contenham todas as palavras da pesquisa. a questão é: o arquivo (como um todo) contém todas as palavras, mesmo que nem todas apareçam no mesmo parágrafo.
glenn jackman

obrigado. alterado se o arquivo inteiro for necessário, então deve usar -0777
kurumi

4

Isso pesquisa várias palavras em vários arquivos:

egrep 'abc|xyz' file1 file2 ..filen 

2
Além de localizar arquivos com ambas as strings, também encontrará arquivos com 'abc' OU 'xyz' sozinho. Acho que o OP estava solicitando arquivos que contêm 'abc' E 'xyz'.
Chris Warth

3

Simplesmente:

grep 'word1\|word2\|word3' *

veja esta postagem para mais informações


Eu adicionaria o -lsinalizador, mas fora isso, essa resposta parece a mais direta para mim, a menos que esteja faltando alguma coisa.
xdhmoore

Sim, também é mais eficiente, pois você não processa todos os dados em vários tubos e filtros
moshe beeri

3
A pergunta pergunta sobre uma expressão que retorna arquivos contendo todos os três termos; isso retorna linhas (em vez de nomes de arquivos) contendo qualquer um dos três (em vez de todos os três).
Benjamin W.

2

Esta é uma mistura das respostas de glenn jackman e kurumi que permite um número arbitrário de regexes em vez de um número arbitrário de palavras fixas ou um conjunto fixo de regexes.

#!/usr/bin/awk -f
# by Dennis Williamson - 2011-01-25

BEGIN {
    for (i=ARGC-2; i>=1; i--) {
        patterns[ARGV[i]] = 0;
        delete ARGV[i];
    }
}

{
    for (p in patterns)
        if ($0 ~ p)
            matches[p] = 1
            # print    # the matching line could be printed
}

END {
    for (p in patterns) {
        if (matches[p] != 1)
            exit 1
    }
}

Execute assim:

./multigrep.awk Dansk Norsk Svenska 'Language: .. - A.*c' dvdfile.dat

2

Aqui está o que funcionou bem para mim:

find . -path '*/.svn' -prune -o -type f -exec gawk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print FILENAME }' {} \;
./path/to/file1.sh
./another/path/to/file2.txt
./blah/foo.php

Se eu quisesse apenas encontrar arquivos .sh com esses três, poderia ter usado:

find . -path '*/.svn' -prune -o -type f -name "*.sh" -exec gawk '/Dansk/{a=1}/Norsk/{b=1}/Svenska/{c=1}END{ if (a && b && c) print FILENAME }' {} \;
./path/to/file1.sh

1

Expandindo a resposta awk de @kurumi, aqui está uma função bash:

all_word_search() {
    gawk '
        BEGIN {
            for (i=ARGC-2; i>=1; i--) {
                search_terms[ARGV[i]] = 0;
                ARGV[i] = ARGV[i+1];
                delete ARGV[i+1];
            }
        }
        {
            for (i=1;i<=NF; i++) 
                if ($i in search_terms) 
                    search_terms[$1] = 1
        }
        END {
            for (word in search_terms) 
                if (search_terms[word] == 0) 
                    exit 1
        }
    ' "$@"
    return $?
}

Uso:

if all_word_search Dansk Norsk Svenska filename; then
    echo "all words found"
else
    echo "not all words found"
fi

1

Eu fiz isso com duas etapas. Faça uma lista de arquivos csv em um arquivo Com a ajuda dos comentários desta página, fiz duas etapas sem script para obter o que precisava. Basta digitar no terminal:

$ find /csv/file/dir -name '*.csv' > csv_list.txt
$ grep -q Svenska `cat csv_list.txt` && grep -q Norsk `cat csv_list.txt` && grep -l Dansk `cat csv_list.txt`

fez exatamente o que eu precisava - imprimir nomes de arquivos contendo todas as três palavras.

Lembre-se também dos símbolos como `' "


1

Se você só precisa de dois termos de pesquisa, provavelmente a abordagem mais legível é executar cada pesquisa e cruzar os resultados:

 comm -12 <(grep -rl word1 . | sort) <(grep -rl word2 . | sort)

1

Se você tem git instalado

git grep -l --all-match --no-index -e Dansk -e Norsk -e Svenska

O --no-index procura arquivos no diretório atual que não é gerenciado pelo Git. Portanto, este comando funcionará em qualquer diretório, independentemente de ser um repositório git ou não.


0

Tive esse problema hoje, e todos os one-liners aqui falharam porque os arquivos continham espaços nos nomes.

Isso é o que eu descobri que funcionou:

grep -ril <WORD1> | sed 's/.*/"&"/' | xargs grep -il <WORD2>
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.