web-dev-qa-db-fra.com

Comment compter le nombre d'un caractère spécifique dans chaque ligne?

Je me demandais comment compter le nombre d'un caractère spécifique dans chaque ligne par certains utilitaires de traitement de texte?

Par exemple, pour compter " dans chaque ligne du texte suivant

"hello!" 
Thank you!

La première ligne en a deux et la deuxième ligne a 0.

Un autre exemple est de compter ( dans chaque ligne.

97
Tim

Vous pouvez le faire avec sed et awk:

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

dat est votre exemple de texte, sed supprime (pour chaque ligne) tous les caractères non - " Et awk imprime pour chaque ligne sa taille (ie length est équivalent à length($0), où $0 désigne la ligne courante).

Pour un autre personnage, il suffit de changer l'expression sed. Par exemple pour ( Pour:

's/[^(]//g'

Mise à jour:sed est une sorte de surpuissance pour la tâche - tr est suffisant. Une solution équivalente avec tr est:

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

Cela signifie que tr supprime tous les caractères qui ne sont pas (-c Signifie complément) dans le jeu de caractères "\n.

115
maxschlepzig

Je voudrais juste utiliser awk

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

Ici, nous définissons le séparateur de champ (avec l'indicateur -F) comme étant le caractère " alors tout ce que nous faisons est d'imprimer le nombre de champs NF - 1. Le nombre d'occurrences du caractère cible sera inférieur de un au nombre de champs séparés.

Pour les personnages amusants qui sont interprétés par le Shell, il vous suffit de vous assurer de leur échapper sinon la ligne de commande essaiera de les interpréter. Donc pour les deux " et ) vous devez sortir du séparateur de champs (avec \).

52
Martin York

Utilisation de tr ard wc:

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

Usage:

$ 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
15
Stéphane Gimenez

Encore une autre implémentation qui ne repose pas sur des programmes externes, dans bash, zsh, yash et certaines implémentations/versions de ksh:

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

Utilisation line="${line//[!(]}"pour compter (.

11
enzotib

Les réponses utilisant awk échouent si le nombre de correspondances est trop important (ce qui se trouve être ma situation). Pour la réponse de loki-astari , l'erreur suivante est signalée:

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

Pour la réponse de enzotib (et l'équivalent de manatwork ), une erreur de segmentation se produit:

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

La solution sed de maxschlepzig fonctionne correctement, mais est lente (temporisations ci-dessous).

Quelques solutions non encore suggérées ici. Tout d'abord, en utilisant grep:

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

Et en utilisant Perl:

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

Voici quelques synchronisations pour quelques-unes des solutions (ordonnées du plus lent au plus rapide); J'ai limité les choses à une seule ligne ici. 'foo.txt' est un fichier avec une ligne et une longue chaîne qui contient 84922 correspondances.

## 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
10
josephwb

Une autre awk solution:

awk '{print gsub(/"/, "")}'
9
Stéphane Chazelas

Une autre implémentation possible avec awk et gsub:

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

La fonction gsub est l'équivalent du sed 's///g' De sed.

Utilisez gsub("[^(]", "") pour compter (.

8
enzotib

J'ai décidé d'écrire un programme C parce que je m'ennuyais.

Vous devriez probablement ajouter une validation d'entrée, mais à part cela, tout est défini.

#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);
}
6
user606723

Pour une chaîne, la plus simple serait avec tr et wc (pas besoin de surpasser avec awk ou sed) - mais notez les commentaires ci-dessus à propos de tr, compte les octets, pas les caractères -

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

$x est la variable qui contient la chaîne (pas un fichier) à évaluer.

6
Ocumo

Voici une autre solution C qui ne nécessite que STD C et moins de mémoire:

#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;
}
4
maxschlepzig

Peut-être une réponse plus simple et purement awk serait d'utiliser split. Split prend une chaîne et la transforme en tableau, la valeur de retour est le nombre d'éléments de tableau générés + 1.

Le code suivant imprimera le nombre de fois "apparaît sur chaque ligne.

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

plus d'informations sur la scission http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_92.html

3
bleurp

Nous pouvons utiliser grep avec regex pour le rendre plus simple et plus puissant.

Pour compter un caractère spécifique.

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

Pour compter les caractères spéciaux, y compris les espaces.

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

Ici, nous sélectionnons n'importe quel caractère avec [\S\s] et avec -o option nous créons grep pour imprimer chaque correspondance (c'est-à-dire chaque caractère) sur une ligne distincte. Et puis utilisez wc -l pour compter chaque ligne.

3
Kannan Mohan

Pour une solution bash pure (cependant, elle est spécifique à bash): Si $x est la variable contenant votre chaîne:

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

Le ${x// chose supprime tous les caractères sauf ", ${#x2} calcule la durée de ce repos.

(Suggestion originale utilisant expr qui a des problèmes, voir commentaires:)

expr length "${x//[^\"]/}"
2
Marian

Voici un simple script Python pour trouver le nombre de " dans chaque ligne d'un fichier:

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

Ici, nous avons utilisé la méthode count de type str intégré.

2
heemayl

Remplacez a par le caractère à compter. La sortie est le compteur de chaque ligne.

Perl -nE 'say y!a!!'
2
JJoao

Comparaison temporelle des solutions présentées (pas une réponse)

L'efficacité des réponses n'est pas importante. Néanmoins, en suivant l'approche @josephwb, j'ai essayé de chronométrer toutes les réponses présentées.

J'utilise comme entrée la traduction portugaise de Victor Hugo "Les Misérables" (grand livre!) Et compte les occurrences de "a". Mon édition comporte 5 volumes, de nombreuses pages ...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

Les réponses C ont été compilées avec gcc, (aucune optimisation).

Chaque réponse a été exécutée 3 fois et a choisi la meilleure.

Ne faites pas trop confiance à ces chiffres (ma machine fait d'autres tâches, etc.). Je partage ces moments avec vous, car j'ai obtenu des résultats inattendus et je suis sûr que vous en trouverez d'autres ...

  • 14 des 16 solutions chronométrées ont pris moins de 1s; 9 moins de 0,1 s, beaucoup utilisant des tuyaux
  • 2 solutions, en utilisant bash ligne par ligne, ont traité les 30k lignes en créant de nouveaux processus, calculent la bonne solution en 10s/20s.
  • grep -oP a est un arbre plus rapide que grep -o a (10; 11 contre 12)
  • La différence entre C et les autres n'est pas aussi grande que ce à quoi je m'attendais. (7; 8 contre 2; 3)
  • (conclusions bienvenues)

(résultats dans un ordre aléatoire)

=========================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
2
JJoao
grep -n -o \" file | sort -n | uniq -c | cut -d : -f 1

où grep fait tout le gros du travail: rapporte chaque caractère trouvé à chaque numéro de ligne. Le reste consiste simplement à additionner le nombre par ligne et à formater la sortie.

Retirer le -n et obtenir le nombre de l'ensemble du fichier.

Compter un fichier texte de 1,5 Mo en moins de 0,015 seconde semble rapide.
Et fonctionne avec des caractères (pas d'octets).

1
user79743

Une solution pour bash. Aucun programme externe appelé (plus rapide pour les chaînes courtes).

Si la valeur est dans une variable:

$ a='"Hello!"'

Cela affichera combien " il contient:

$ b="${a//[^\"]}"; echo "${#b}"
2
1
Isaac