Como contar o número de ocorrências parciais de uma string em um arquivo

5

Eu tenho um arquivo do qual preciso contar todas as correspondências parciais de uma string de entrada em um arquivo.
Vou mostrar um exemplo fácil do que preciso:

Em um arquivo com este conteúdo:

Good-Black-Cat
Bad-Red-Cat
Bad-Gray-Dog
Good-Golden-Dog
Bad-White-Dog
Good-Tabby-Cat
Bad-Siamese-Cat

Eu preciso contar quantas vezes a string parcial "Good - * - Cat" (onde * poderia ser qualquer coisa, não importa) aparece. A contagem de saída esperada é 2.

Qualquer ajuda será apreciada.

    
por Rodrigo Andres Nava Lara 26.05.2018 / 23:47

3 respostas

12

Dado

$ cat file
Good-Black-Cat
Bad-Red-Cat
Bad-Gray-Dog
Good-Golden-Dog
Bad-White-Dog
Good-Tabby-Cat
Bad-Siamese-Cat

então

$ grep -c 'Good-.*-Cat' file
2

Observe que essa é uma contagem de linhas correspondentes - por exemplo, não funcionará para várias ocorrências por linha ou para ocorrências que abranjam linhas.

Como alternativa, com awk

awk '/Good-.*-Cat/ {n++} END {print n}' file

Se você precisar corresponder várias ocorrências possíveis por linha, sugiro perl :

perl -lne '$c += () = /Good-.*?-Cat/g }{ print $c' file

em que /Good-.*?-Cat/g corresponde a várias vezes ( g ) e não-avidamente * ( .*? ) e a atribuição () = força as correspondências a serem avaliadas em um contexto escalar para que pode adicioná-los à contagem.

Como alternativa, você pode usar grep no modo de expressão regular perl-comparavel (PCRE), para habilitar o modificador não-ganancioso, com -o para exibir apenas as partes correspondentes e depois contar com wc :

grep -Po 'Good-.*?-Cat' file | wc -l

Se você também precisa coincidir com ocorrências que podem se estender por um limite de linha, você pode fazer isso em perl desmarcando o separador de registro (nota: isso significa que todo o arquivo foi sugado para a memória) e adicionando o s regex modifier eg

perl -0777 -nE '$c += () = /Good-.*?-Cat/gs }{ say $c' file
    
por steeldriver 26.05.2018 / 23:56
4

awk, várias ocorrências, separadas por espaço

$ awk '{for(i=1;i<=NF;i++ ) count+=match($i,/Good-.*-Cat/)};END{print count}' input.txt
4
$ cat input.txt
Good-Black-Cat
Bad-Red-Cat
Bad-Gray-Dog
Good-Golden-Dog Good-Whatever-Cat Good-Something-Cat
Bad-White-Dog
Good-Tabby-Cat
Bad-Siamese-Cat

sed + wc, ocorrências não múltiplas

Isso usa correspondência de padrão negativo //! com d para exclusão, deixando apenas linhas de interesse.

$ sed '/Good-.*-Cat/!d' input.txt
Good-Black-Cat
Good-Golden-Dog Good-Whatever-Cat
Good-Tabby-Cat
$ sed '/Good-.*-Cat/!d' input.txt | wc -l
3

Solução da Shell, ocorrências não múltiplas

Aqui está uma maneira de shell que combina case...esac e loop de leitura de arquivo:

$ n=0; while IFS= read -r line || [ -n "$line" ]; do case "$line" in "Good-"*"-Cat") n=$((n+1));; esac; done < input.txt; echo "$n"
2

Ou com indentação

n=0
while IFS= read -r line || [ -n "$line" ]; do 
    case "$line" in 
        "Good-"*"-Cat") n=$((n+1));; 
    esac
done < input.txt
echo "$n"

Explicação:

  • n=0 initializes n variável do contador
  • while IFS= read -r line || [ -n "$line" ]; do...done < input.txt é o loop de leitura de arquivo padrão usado no shell script, com || [ -n "$line" ] protection para considerar possíveis arquivos que não terminam em nova linha
  • case "$line" in "Good-"*"-Cat") n=$((n+1));; esac correspondência de padrões para a cadeia desejada com $((...)) de expansão aritmética para incrementar a variável do contador.
por Sergiy Kolodyazhnyy 27.05.2018 / 00:58
3

Versão sed / grep não extravagante

sed 's/\(Good-[^ ]*-Cat\)/XXXX\n/g' input.txt | grep -c XXXX

Enquanto XXXX pode ser qualquer padrão que não aparece em seu arquivo. Essa abordagem substitui todas as correspondências com o padrão XXXX e uma nova linha, para que seja facilmente contável por uma expressão grep básica.

A propósito, se você pegar "Onde * poderia ser qualquer coisa" literalmente, pelo menos no meu entendimento, a saída de qualquer programa seria sempre 0 ou 1, então estou assumindo que ele não deveria conter um espaço .

    
por Sebastian Stark 27.05.2018 / 09:27