Como encontrar todos os arquivos contendo várias strings de uma longa lista de combinações de strings?

6

Ainda sou muito novo em ferramentas de linha de comando (usando meu terminal Mac OSX) e espero não ter perdido a resposta em outro lugar, mas procurei por horas.

Eu tenho um arquivo de texto (vamos chamá-lo strings.txt) contendo 200 combinações de 3 strings. [ Edit 2017/01/30 ] As primeiras cinco linhas são assim:

"surveillance data" "surveillance technology" "cctv camera"
"social media" "surveillance techniques" "enforcement agencies"
"social control" "surveillance camera" "social security"
"surveillance data" "security guards" "social networking"
"surveillance mechanisms" "cctv surveillance" "contemporary surveillance"

Note que posso alterar o strings.txt para qualquer outro formato, desde que as frases do bigrama / 2 palavras, como dados de vigilância na linha 1, permaneçam juntas. (Isso significa que eu posso excluir as citações, se necessário, como para a resposta por @MichaelVehrs abaixo).

Agora, quero pesquisar um diretório com mais de 800 arquivos para os arquivos que contêm pelo menos uma das combinações de string (em qualquer parte do arquivo). Minha ideia original era usar egrep com um arquivo padrão como este:

egrep -i -l -r -f strings.txt file_directory

No entanto, só posso fazer isso funcionar se houver uma string por linha. Isso não é desejável, porque eu preciso dos arquivos identificados para conter todas as três seqüências de caracteres de um determinado padrão. Existe uma maneira de adicionar algum tipo de operador AND ao arquivo padrão do grep? Ou existe outra maneira de conseguir o que eu quero usando outra função / ferramenta? Muito obrigado!

Editar 2017/01/30

A resposta de @MichaelVehrs abaixo foi muito útil; Eu editei para o seguinte:

while read one two three four five six
do grep -ilFr "$one $two" *files* | xargs grep -ilFr "$three $four" |  xargs grep -ilFr "$five $six"
done < *patternfile* | sort -u

Essa resposta funciona quando o arquivo padrão contém as strings sem aspas. Infelizmente, parece apenas corresponder ao padrão na primeira linha do arquivo padrão. Alguém sabe por quê?

Editar 2017/01/29

Uma pergunta semelhante sobre o grepping de vários valores foi feita antes , mas eu preciso da lógica AND para poder Corresponda uma das combinações de três cadeias do arquivo padrão strings.txt nos outros arquivos. Eu percebo que o formato de strings.txt talvez precise ser alterado para que a correspondência funcione e gostaria de receber sugestões.

    
por ViolaW 30.01.2017 / 00:46

4 respostas

1

Como agrep parece não estar presente em seu sistema, dê uma olhada nesta alternativa com base em sed e awk para aplicar grep com e operação a partir de padrões lidos por um arquivo local.

PS: Já que você usa o osx, não tenho certeza se a versão do awk tem suporte para uso abaixo.

awk pode simular o grep com AND operação de múltiplos padrões neste uso:
awk '/pattern1/ && /pattern2/ && /pattern3/'

Você pode transformar o seu arquivo padrão a partir disso:

$ cat ./tmp/d1.txt
"surveillance data" "surveillance technology" "cctv camera"
"social media" "surveillance techniques" "enforcement agencies"
"social control" "surveillance camera" "social security"
"surveillance data" "security guards" "social networking"
"surveillance mechanisms" "cctv surveillance" "contemporary surveillance"

Para isso:

$ sed 's/" "/\/ \&\& \//g; s/^"/\//g; s/"$/\//g' ./tmp/d1.txt
/surveillance data/ && /surveillance technology/ && /cctv camera/
/social media/ && /surveillance techniques/ && /enforcement agencies/
/social control/ && /surveillance camera/ && /social security/
/surveillance data/ && /security guards/ && /social networking/
/surveillance mechanisms/ && /cctv surveillance/ && /contemporary surveillance/

PS: Você pode redirecionar a saída para outro arquivo usando >anotherfile no final ou usar a opção sed -i para fazer alterações no local no mesmo arquivo padrão de termos de pesquisa.

Então você só precisa alimentar o awk com padrões formatados por awk a partir desse arquivo padrão:

$ while IFS= read -r line;do awk "$line" *.txt;done<./tmp/d1.txt #d1.txt = my test pattern file

Você também não pode transformar os padrões em seu arquivo de padrão original aplicando sed em cada linha desse arquivo de padrão original da seguinte forma:

while IFS= read -r line;do 
  line=$(sed 's/" "/\/ \&\& \//g; s/^"/\//g; s/"$/\//g' <<<"$line")
  awk "$line" *.txt
done <./tmp/d1.txt

Ou como uma linha:

$ while IFS= read -r line;do line=$(sed 's/" "/\/ \&\& \//g; s/^"/\//g; s/"$/\//g' <<<"$line"); awk "$line" *.txt;done <./tmp/d1.txt

Os comandos acima retornam os resultados corretos de AND em meus arquivos de teste com esta aparência:

$ cat d2.txt
This guys over there have the required surveillance technology to do the job.
The other guys not only have efficient surveillance technology, but they also gather surveillance data by one cctv camera.

$ cat d3.txt
All surveillance data are locked.
All surveillance data are locked and guarded by security guards.
There are several surveillance mechanisms (i.e cctv surveillance, contemporary surveillance, etv)

Resultados:

$ while IFS= read -r line;do awk "$line" *.txt;done<./tmp/d1.txt
#or while IFS= read -r line;do line=$(sed 's/" "/\/ \&\& \//g; s/^"/\//g; s/"$/\//g' <<<"$line"); awk "$line" *.txt;done <./tmp/d1.txt
The other guys not only have efficient surveillance technology, but they also gather surveillance data by one cctv camera.
There are several surveillance mechanisms (i.e cctv surveillance, contemporary surveillance, etv)

Atualização:
A solução awk acima exibe o conteúdo dos arquivos txt correspondentes.
Se você deseja exibir os nomes dos arquivos em vez do conteúdo, use o seguinte awk, se necessário:

awk "$line""{print FILENAME}" *.txt
    
por 01.02.2017 / 11:05
2

O problema é um pouco estranho, mas você pode se aproximar assim:

while read one two three four five six
  do grep -lF "$one $two" *files* | xargs grep -lF "$three $four" | xargs grep -lF "$five $six"
done < patterns | sort -u

Isso pressupõe que o arquivo padrão contenha exatamente seis palavras por linha (três padrões de duas palavras cada). O and lógico é realizado encadeando três filtros consecutivos ( grep ). Note que isto não é particularmente eficiente. Uma solução awk provavelmente seria mais rápida.

    
por 30.01.2017 / 08:38
2

Eu usaria perl , algo como:

perl -MFile::Find -MClone=clone -lne '
  # parse the strings.txt input, here looking for the sequences of
  # 0 or more characters (.*?) in between two " characters
  for (/"(.*?)"/g) {
    # @needle is an array of associative arrays whose keys
    # are the "strings" for each line.
    $needle[$n]{$_} = undef;
  }
  $n++;

  END{
    sub wanted {
      return unless -f; # only regular files
      my $needle_clone = clone(\@needle);
      if (open FILE, "<", $_) {
        LINE: while (<FILE>) {
          # read the file line by line
          for (my $i = 0; $i < $n; $i++) {
            for my $s (keys %{$needle_clone->[$i]}) {
              if (index($_, $s)>=0) {
                # if the string is found, we delete it from the associative
                # array.
                delete $needle_clone->[$i]{$s};
                unless (%{$needle_clone->[$i]}) {
                  # if the associative array is empty, that means we have
                  # found all the strings for that $i, that means we can
                  # stop processing, and the file matches
                  print $File::Find::name;
                  last LINE;
                }
              }
            }
          }
        }
        close FILE;
      }
    }
    find(\&wanted, ".")
  }' /path/to/strings.txt

Isso significa que minimizamos o número de pesquisas de string.

Aqui, estamos processando os arquivos linha por linha. Se os arquivos forem razoavelmente pequenos, você poderia processá-los como um todo, o que simplificaria um pouco e poderia melhorar o desempenho.

Observe que espera que o arquivo de lista esteja no:

 "surveillance data" "surveillance technology" "cctv camera"
 "social media" "surveillance techniques" "enforcement agencies"
 "social control" "surveillance camera" "social security"
 "surveillance data" "security guards" "social networking"
 "surveillance mechanisms" "cctv surveillance" "contemporary surveillance"

formato, com um número (não precisa ser 3) de strings entre aspas (com aspas duplas) em cada linha. As strings entre aspas não podem conter caracteres de aspas duplas. O caracter de aspas duplas não faz parte do texto que está sendo pesquisado. Isto é, se o arquivo de lista continha:

"A" "B"
"1" "2" "3"

que informaria o caminho de todos os arquivos regulares no diretório atual e abaixo dele ou

  • ambos A e B
  • ou (não sendo um exclusivo ou ) todos os 1 , 2 e 3

em qualquer lugar neles.

    
por 30.01.2017 / 15:56
1

Esta é outra abordagem que parece funcionar nos meus testes.

Copiei os dados do seu arquivo de strings para um arquivo chamado d1.txt e movi-o para um diretório separado (ou seja, tmp) para evitar que o grep mais tarde correspondesse ao arquivo de strings no mesmo arquivo (d1.txt).

Em seguida, insira neste arquivo de strings (d1.txt no meu caso) um ponto-e-vírgula entre cada termo de pesquisa com o seguinte comando: sed -i 's/" "/";"/g' ./tmp/d1.txt

$ cat ./tmp/d1.txt
"surveillance data" "surveillance technology" "cctv camera"
"social media" "surveillance techniques" "enforcement agencies"
"social control" "surveillance camera" "social security"
"surveillance data" "security guards" "social networking"
"surveillance mechanisms" "cctv surveillance" "contemporary surveillance"
$ sed -i 's/" "/";"/g' ./tmp/d1.txt
$ cat ./tmp/d1.txt
"surveillance data";"surveillance technology";"cctv camera"
"social media";"surveillance techniques";"enforcement agencies"
"social control";"surveillance camera";"social security"
"surveillance data";"security guards";"social networking"
"surveillance mechanisms";"cctv surveillance";"contemporary surveillance"

Em seguida, remova as aspas duplas usando o comando sed 's/"//g' ./tmp/d1.txt PS: Isso pode não ser realmente necessário, mas eu removi aspas duplas para testes.

$ sed -i 's/"//g' ./tmp/d1.txt && cat ./tmp/d1.txt
surveillance data;surveillance technology;cctv camera
social media;surveillance techniques;enforcement agencies
social control;surveillance camera;social security
surveillance data;security guards;social networking
surveillance mechanisms;cctv surveillance;contemporary surveillance

Não, você pode grep todos os arquivos no diretório atual com o programa agrep , que é projetado exatamente para fornecer o grep multi-padrão com a operação AND.

agrep requer que vários padrões sejam separados por ponto e vírgula ; para que seja avaliado como AND.

Nos meus testes, criei dois arquivos de amostra com conteúdo:

$ cat d2.txt
This guys over there have the required surveillance technology to do the job.

The other guys not only have efficient surveillance technology, but they also gather surveillance data by one cctv camera.

$ cat d3.txt
All surveillance data are locked.
All surveillance data are locked and guarded by security guards.
There are several surveillance mechanisms (i.e cctv surveillance, contemporary surveillance, etv)

A execução de agrep no diretório atual retorna as linhas corretas (com AND) e nomes de arquivos:

$ while IFS= read -r line;do agrep "$line" *;done<./tmp/d1.txt
d2.txt: The other guys not only have efficient surveillance technology, but they also gather surveillance data by one cctv camera.
d3.txt: There are several surveillance mechanisms (i.e cctv surveillance, contemporary surveillance, etv)
    
por 30.01.2017 / 15:55