Como contar o número de um caractere específico em cada linha?

77

Eu queria saber como contar o número de um caractere específico em cada linha por alguns utilitários de processamento de texto?

Por exemplo, para contar " em cada linha do texto a seguir

"hello!" 
Thank you!

A primeira linha tem dois e a segunda linha tem 0.

Outro exemplo é contar ( em cada linha.

    
por Tim 14.08.2011 / 20:31

18 respostas

94

Você pode fazer isso com sed e awk :

$ sed 's/[^"]//g' dat | awk '{ print length }'
2
0

Onde dat é o texto do seu exemplo, sed exclui (para cada linha) todos os caracteres que não são " e awk imprime para cada linha seu tamanho (ou seja, length é equivalente a length($0) , onde $0 indica a linha atual).

Para outro personagem, você só precisa mudar a expressão sed. Por exemplo, para ( para:

's/[^(]//g'

Atualização: sed é um pouco exagerado para a tarefa - tr é suficiente. Uma solução equivalente com tr é:

$ tr -d -c '"\n' < dat | awk '{ print length; }'

Isso significa que tr exclui todos os caracteres que não são ( -c complemento) no conjunto de caracteres "\n .

    
por 14.08.2011 / 21:06
44

Gostaria apenas de usar o awk

awk -F\" '{print NF-1}' <fileName>

Aqui, definimos o separador de campo (com o sinalizador -F) como o caractere " , então tudo o que fazemos é imprimir o número de campos NF - 1. O número de ocorrências do caractere de destino será um a menos que o número de campos separados.

Para caracteres engraçados que são interpretados pelo shell, você só precisa se certificar de que você os escape, caso contrário, a linha de comando tentará interpretá-los. Portanto, para os dois " e ) , você precisa escapar do separador de campo (com \ ).

    
por 15.08.2011 / 00:47
14

Usando tr ard wc :

function countchar()
{
    while IFS= read -r i; do printf "%s" "$i" | tr -dc "$1" | wc -m; done
}

Uso:

$ countchar '"' <file.txt  #returns one count per line of file.txt
1
3
0

$ countchar ')'           #will count parenthesis from stdin
$ countchar '0123456789'  #will count numbers from stdin
    
por 14.08.2011 / 20:37
10

As respostas usando awk falharão se o número de correspondências for muito grande (o que acontece é a minha situação). Para obter a resposta de loki-astari , o seguinte erro é relatado:

awk -F" '{print NF-1}' foo.txt 
awk: program limit exceeded: maximum number of fields size=32767
    FILENAME="foo.txt" FNR=1 NR=1

Para a resposta de enzotib (e o equivalente de manatwork ), ocorre uma falha de segmentação:

awk '{ gsub("[^\"]", ""); print length }' foo.txt
Segmentation fault

A solução sed de maxschlepzig funciona corretamente, mas é lenta (intervalos abaixo).

Algumas soluções ainda não sugeridas aqui. Primeiro, usando grep :

grep -o \" foo.txt | wc -w

e usando perl :

perl -ne '$x+=s/\"//g; END {print "$x\n"}' foo.txt

Aqui estão algumas temporizações para algumas das soluções (ordenadas da mais lenta para a mais rápida); Eu limito as coisas para one-liners aqui. 'foo.txt' é um arquivo com uma linha e uma longa cadeia de caracteres que contém 84922 correspondências.

## sed solution by [maxschlepzig]
$ time sed 's/[^"]//g' foo.txt | awk '{ print length }'
84922
real    0m1.207s
user    0m1.192s
sys     0m0.008s

## using grep
$ time grep -o \" foo.txt | wc -w
84922
real    0m0.109s
user    0m0.100s
sys     0m0.012s

## using perl
$ time perl -ne '$x+=s/\"//g; END {print "$x\n"}' foo.txt
84922
real    0m0.034s
user    0m0.028s
sys     0m0.004s

## the winner: updated tr solution by [maxschlepzig]
$ time tr -d -c '\"\n' < foo.txt |  awk '{ print length }'
84922
real    0m0.016s
user    0m0.012s
sys     0m0.004s
    
por 01.10.2014 / 21:38
10

No entanto, outra implementação que não depende de programas externos, em bash , zsh , yash e algumas implementações / versões de ksh :

while IFS= read -r line; do 
  line="${line//[!\"]/}"
  echo "${#line}"
done <input-file

Use line="${line//[!(]}" para contar ( .

    
por 14.08.2011 / 22:48
8

Outra implementação possível com o awk e o gsub:

awk '{ gsub("[^\"]", ""); print length }' input-file

A função gsub é o equivalente de 's///g' do sed.

Use gsub("[^(]", "") para contar ( .

    
por 14.08.2011 / 22:12
8

Outra solução awk :

awk '{print gsub(/"/, "")}'
    
por 23.11.2014 / 20:53
6

Eu decidi escrever um programa em C porque estava entediado.

Você provavelmente deve adicionar validação de entrada, mas diferente de tudo isso.

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[])
{
        char c = argv[1][0];
        char * line = NULL;
        size_t len = 0;
        while (getline(&line, &len, stdin) != -1)
        {
                int count = 0;
                char * s = line;
                while (*s) if(*s++ == c) count++;
                printf("%d\n",count);
        }
        if(line) free(line);
}
    
por 15.08.2011 / 01:28
5

Para uma string, o mais simples seria com tr e wc (não é necessário sobrecarregar com awk ou sed ) - mas observe os comentários acima sobre tr , contagens de bytes, não caracteres -

echo $x | tr -d -c '"' | wc -m

onde $x é a variável que contém a string (não um arquivo) para avaliar.

    
por 24.12.2014 / 03:02
4

Aqui está outra solução C que só precisa de STD C e menos memória:

#include <stdio.h>

int main(int argc, char **argv)
{
  if (argc < 2 || !*argv[1]) {
    puts("Argument missing.");
    return 1;
  }
  char c = *argv[1], x = 0;
  size_t count = 0;
  while ((x = getc(stdin)) != EOF)
    if (x == '\n') {
      printf("%zd\n", count);
      count = 0;
    } else if (x == c)
      ++count;
  return 0;
}
    
por 15.08.2011 / 08:18
3

Podemos usar grep com regex para torná-lo mais simples e poderoso.

Para contar um caractere específico.

$ grep -o '"' file.txt|wc -l

Para contar caracteres especiais, incluindo caracteres de espaços em branco.

$ grep -Po '[\W_]' file.txt|wc -l

Aqui estamos selecionando qualquer caractere com [\S\s] e com a opção -o fazemos grep para imprimir cada correspondência (ou seja, cada caractere) em uma linha separada. E, em seguida, use wc -l para contar cada linha.

    
por 23.11.2014 / 18:53
3

Talvez uma resposta mais direta e puramente awk seja usar o split. Split pega uma string e a transforma em uma matriz, o valor de retorno é o número de itens da matriz gerados + 1.

O código a seguir imprime o número de vezes "aparece em cada linha.

awk ' {print (split($0,a,"\"")-1) }' file_to_parse

mais informações sobre link

    
por 13.01.2015 / 18:12
2

Aqui está um script Python simples para encontrar a contagem de " em cada linha de um arquivo:

#!/usr/bin/env python2
with open('file.txt') as f:
    for line in f:
        print line.count('"')

Aqui, usamos o método count do tipo str integrado.

    
por 03.03.2015 / 18:09
2

Para uma solução bash pura (no entanto, ela é específica do bash): Se $x for a variável que contém sua string:

x2="${x//[^\"]/}"
echo ${#x2}

A coisa ${x// remove todos os caracteres, exceto " , ${#x2} calcula a duração desse descanso.

(Sugestão original usando expr que tem problemas, veja os comentários:)

expr length "${x//[^\"]/}"
    
por 25.02.2013 / 18:36
2

Substitua a pelo caractere a ser contado. Saída é o contador para cada linha.

perl -nE 'say y!a!!'
    
por 03.03.2015 / 19:12
2

Comparação de tempo das soluções apresentadas (não uma resposta)

A eficiência das respostas não é importante. No entanto, seguindo a abordagem @josephwb, tentei sincronizar todas as respostas apresentadas.

eu uso como insira a tradução para o português de Victor Hugo "Les Miserables" (grande livro!) e conte as ocorrências de "a". Minha edição tem 5 volumes, muitas páginas ...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

C respostas foram compiladas com o gcc, (sem otimizações).

Cada resposta foi executada 3 vezes e escolhe o melhor.

Não confie muito nesses números (meu máquina está fazendo outras tarefas, etc, etc.). Eu compartilho esses momentos com você, porque eu tenho alguns resultados inesperados e tenho certeza que você vai encontrar mais alguns ...

  • 14 das 16 soluções cronometradas demoraram menos de 1s; 9 menos de 0,1s, muitos deles usando canos
  • 2 soluções, usando o bash linha por linha, processaram as linhas de 30k criando novos processos, calcular a solução correta em 10s / 20s.
  • grep -oP a é a árvore vezes mais rápida que grep -o a (10; 11 vs 12)
  • A diferença entre C e outros não é tão grande quanto eu esperava. (7; 8 vs 2; 3)
  • (conclusões bem-vindas)

(resulta em uma ordem aleatória)

=========================1 maxschlepzig
$ time sed 's/[^a]//g' mis.txt | awk '{print length}' > a2
real    0m0.704s ; user 0m0.716s
=========================2 maxschlepzig
$ time tr -d -c 'a\n' < mis.txt | awk '{ print length; }' > a12
real    0m0.022s ; user 0m0.028s
=========================3 jjoao
$ time perl -nE 'say y!a!!' mis.txt  > a1
real    0m0.032s ; user 0m0.028s
=========================4 Stéphane Gimenez
$ function countchar(){while read -r i; do echo "$i"|tr -dc "$1"|wc -c; done }

$ time countchar "a"  < mis.txt > a3
real    0m27.990s ; user    0m3.132s
=========================5 Loki Astari
$ time awk -Fa '{print NF-1}' mis.txt > a4
real    0m0.064s ; user 0m0.060s
Error : several -1
=========================6 enzotib
$ time awk '{ gsub("[^a]", ""); print length }' mis.txt > a5
real    0m0.781s ; user 0m0.780s
=========================7 user606723
#include <stdio.h> #include <string.h> // int main(int argc, char *argv[]) ...  if(line) free(line); }

$ time a.out a < mis.txt > a6
real    0m0.024s ; user 0m0.020s
=========================8 maxschlepzig
#include <stdio.h> // int main(int argc, char **argv){if (argc < 2 || !*argv[1]) { ...  return 0; }

$ time a.out a < mis.txt > a7
real    0m0.028s ; user 0m0.024s
=========================9 Stéphane Chazelas
$ time awk '{print gsub(/a/, "")}'< mis.txt > a8
real    0m0.053s ; user 0m0.048s
=========================10 josephwb count total
$ time grep -o a < mis.txt | wc -w > a9
real    0m0.131s ; user 0m0.148s
=========================11 Kannan Mohan count total
$ time grep -o 'a' mis.txt | wc -l > a15
real    0m0.128s ; user 0m0.124s
=========================12 Kannan Mohan count total
$ time grep -oP 'a' mis.txt | wc -l > a16
real    0m0.047s ; user 0m0.044s
=========================13 josephwb Count total
$ time perl -ne '$x+=s/a//g; END {print "$x\n"}'< mis.txt > a10
real    0m0.051s ; user 0m0.048s
=========================14 heemayl
#!/usr/bin/env python2 // with open('mis.txt') as f: for line in f: print line.count('"')

$ time pyt > a11
real    0m0.052s ; user 0m0.052s
=========================15 enzotib
$ time  while IFS= read -r line; do   line="${line//[!a]/}"; echo "${#line}"; done < mis.txt  > a13
real    0m9.254s ; user 0m8.724s
=========================16 bleurp
$ time awk ' {print (split($0,a,"a")-1) }' mis.txt > a14
real    0m0.148s ; user 0m0.144s
Error several -1
    
por 04.03.2015 / 02:04
1
grep -n -o \" file | sort -n | uniq -c | cut -d : -f 1

onde grep faz todo o trabalho pesado: relata cada caractere encontrado em cada número de linha. O resto é apenas para somar a contagem por linha e formatar a saída.

Remova o -n e obtenha a contagem do arquivo inteiro.

A contagem de um arquivo de texto de 1.5Meg em menos de 0.015 segundos parece rápida.
E funciona com caracteres (não bytes).

    
por 25.11.2015 / 05:30
1

Uma solução para o bash. Nenhum programa externo chamado (mais rápido para strings curtas).

Se o valor estiver em uma variável:

$ a='"Hello!"'

Isso imprimirá quantos " ele contém:

$ b="${a//[^\"]}"; echo "${#b}"
2
    
por 26.11.2016 / 03:18