Wie zähle ich die Nummer eines bestimmten Zeichens in jeder Zeile?


87

Ich habe mich gefragt, wie die Nummer eines bestimmten Zeichens in jeder Zeile von einigen Textverarbeitungsprogrammen gezählt werden soll.

Zum Beispiel, um "in jeder Zeile des folgenden Textes zu zählen

"hello!" 
Thank you!

Die erste Zeile hat zwei und die zweite Zeile hat 0.

Ein weiteres Beispiel ist das Zählen (in jeder Zeile.


1
Ich möchte nur hinzufügen, dass Sie durch das Schreiben Ihres eigenen 10-zeiligen C-Programms eine wesentlich höhere Leistung erzielt haben, als reguläre Ausdrücke mit sed zu verwenden. Abhängig von der Größe Ihrer Eingabedateien sollten Sie dies in Betracht ziehen.
user606723

Antworten:


104

Sie können es mit sedund tun awk:

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

Wo datist Ihr Beispieltext, sed löscht (für jede Zeile) alle Nicht- "Zeichen und awkdruckt für jede Zeile ihre Größe (dh lengthentspricht length($0), wo $0bezeichnet die aktuelle Zeile).

Für einen anderen Charakter müssen Sie nur den sed-Ausdruck ändern. Zum Beispiel für (:

's/[^(]//g'

Update: Ist sed eine Art Overkill für die Aufgabe - trist ausreichend. Eine äquivalente Lösung mit trist:

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

Das bedeutet, dass tralle Zeichen gelöscht werden, die nicht -cim Zeichensatz enthalten sind ( Komplement bedeuten) "\n.


3
+1 sollte effizienter sein als die tr& wc-Version.
Stéphane Gimenez

1
Ja, aber kann es mit Unicode umgehen?
Amphetamachine

@amphetamachine, ja - zumindest ein Schnelltest mit ß(utf hex: c3 9f) (statt ") wie erwartet funktioniert, das heißt tr, sedund awksie ergänzen / Ersatz / ohne ein Problem zu zählen - auf einem Ubuntu 10.04 - System.
Maxschlepzig

1
Die meisten Versionen von tr, einschließlich GNU tr und klassischem Unix tr, arbeiten mit Einzelbyte-Zeichen und sind nicht Unicode-konform. Zitat aus Wikipedia tr (Unix) . Probieren Sie dieses Snippet aus: echo "aā⧾c" | tr "ā⧾" b... unter Ubuntu 10.04 ... ßist ein Einzelbyte Erweitertes lateinisches Zeichen und wird behandelt von tr... Das eigentliche Problem hierbei ist nicht, dass trUnicode nicht behandelt wird (da ALLE Zeichen Unicode sind), sondern dass jeweils trnur ein Byte behandelt wird.
Peter.O

@fred, nein, ß ist kein Einzelbyte-Zeichen - seine Unicode-Position ist U + 00DF, das in UTF-8 als 'c3 9f' codiert ist, dh zwei Bytes.
Maxschlepzig

49

Ich würde einfach awk benutzen

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

Hier setzen wir das Feldtrennzeichen (mit dem -F-Flag) auf das Zeichen, "dann drucken wir nur die Anzahl der Felder NF- 1. Die Anzahl der Vorkommen des Zielzeichens ist eins weniger als die Anzahl der getrennten Felder.

Für lustige Zeichen, die von der Shell interpretiert werden, müssen Sie nur sicherstellen, dass Sie sie maskieren. Andernfalls versucht die Befehlszeile, sie zu interpretieren. Also für beide "und )Sie müssen das Feldtrennzeichen (mit \) zu entkommen .


1
Bearbeiten Sie Ihre Antwort möglicherweise, um stattdessen einzelne Anführungszeichen für die Flucht zu verwenden. Es funktioniert mit jedem Zeichen (außer '). Es hat auch ein seltsames Verhalten mit leeren Zeilen.
Stéphane Gimenez

Die Frage wird speziell verwendet, "daher fühle ich mich verpflichtet, den Code damit arbeiten zu lassen. Es kommt darauf an, welche Shell Sie verwenden, wenn der Charakter entkommen muss, aber bash / tcsh müssen beide entkommen "
Martin York,

Natürlich, aber es gibt kein Problem mit -F'"'.
Stéphane Gimenez

+1 Was für eine gute Idee, FS zu verwenden ... Dadurch wird die Leerzeile mit -1 und beispielsweise "$ 1" in der Bash-Befehlszeile aufgelöst. ...awk -F"$1" '{print NF==0?NF:NF-1}' filename
Peter.O

Auch mit mehreren Zeichen als Trennzeichen arbeiten ... nützlich!
COil

14

Mit trard wc:

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

Verwendungszweck:

$ 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

3
Hinweis. trbehandelt keine Zeichen, die mehr als ein Byte verwenden .. siehe Wikipedia tr (Unix) .. dh. trist nicht Unicode-konform.
Peter.O


Sie müssen Leerzeichen von entfernen $IFS, andernfalls readwerden sie von Anfang bis Ende gekürzt.
Stéphane Chazelas


@ Peter.O, einige trImplementierungen unterstützen Multibyte-Zeichen, zählen aber wc -cBytes, keine Zeichen ( wc -mZeichen erforderlich ).
Stéphane Chazelas

11

Noch eine andere Implementierung , die auf externe Programme nicht verlassen, in bash, zsh, yashund einige Implementierungen / Versionen ksh:

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

Verwenden line="${line//[!(]}"für die Zählung (.


Wenn in der letzten Zeile kein \ n nachgestellt ist, wird die while-Schleife beendet, da sie, obwohl sie die letzte Zeile gelesen hat, auch einen Exit-Code ungleich Null zurückgibt, der EOF angibt. Um dies zu umgehen, funktioniert das folgende Snippet (..Es hat mich eine Weile nervt, und ich habe gerade dieses eof=false; IFS=; until $eof; do read -r || eof=true; echo "$REPLY"; done
workaroung

@ Gilles: Sie haben ein Trailing hinzugefügt /, das in bash nicht benötigt wird. Es ist eine ksh-Anforderung?
Enzotib

1
Das Trailing /wird in älteren Versionen von ksh und IIRC auch in älteren Versionen von bash benötigt.
Gilles

10

Die Antworten mit awkfehlschlagen , wenn die Anzahl der Spiele zu groß ist (was meine Situation passiert sein). Für die Antwort von loki-astari wird folgender Fehler gemeldet:

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

Bei der Antwort von enzotib (und der Entsprechung von manatwork ) tritt ein Segmentierungsfehler auf:

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

Die sedLösung von maxschlepzig funktioniert korrekt, ist aber langsam (Timings unten).

Einige hier noch nicht vorgeschlagene Lösungen. Erstens mit grep:

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

Und mit perl:

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

Hier sind einige Zeitangaben für einige der Lösungen (am langsamsten bis am schnellsten geordnet). Ich habe mich hier auf Einzeiler beschränkt. 'foo.txt' ist eine Datei mit einer Zeile und einer langen Zeichenfolge, die 84922 Übereinstimmungen enthält.

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

+ gute Idee! Ich erweiterte Ihre Tabelle, in einer neuen Antwort, zögern Sie nicht zu bearbeiten (das endgültige Bild ist nicht so klar, aber ich glaube, @maxschlepzig ist Stahl die schnellere Lösung)
JJoao

Die Lösung von maxschlepzig ist superschnell!
Okwap


8

Eine weitere mögliche Implementierung mit awk und gsub:

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

Die Funktion gsubentspricht der von sed 's///g'.

Verwenden gsub("[^(]", "")für die Zählung (.


Sie können ein Zeichen speichern, dh beim Entfernen der stdin-Umleitung ...;)
maxschlepzig

@maxschlepzig: ja, natürlich;)
Enzotib

1
awk '{print gsub(/"/,"")}' input-fileEs würde ausreichen, wenn Sie für jede Teilzeichenfolge, die dem regulären Ausdruck r in der Zeichenfolge t entspricht, die Zeichenfolge s einsetzen und die Anzahl der Ersetzungen zurückgeben. (man awk)
manatwork

6

Ich beschloss, ein C-Programm zu schreiben, weil ich gelangweilt war.

Sie sollten wahrscheinlich eine Eingabevalidierung hinzufügen, aber ansonsten ist alles festgelegt.

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

Vielen Dank! Vielen Dank für die Langeweile, damit ich etwas lernen kann. Oh warte, brauchst du eine Rückkehr?
Tim

* achselzucken * , wenn du völlig korrekt sein willst, musst du auch noch ein paar #includes hinzufügen, aber die Standardwarnungen auf meinem Compiler scheinen das nicht zu interessieren.
user606723

Sie können das weglassen, free(line)weil das Beenden des Programms implizit den gesamten zugewiesenen Speicher freigibt - dann ist Platz für ein return 0;...;). Selbst in Beispielen ist es nicht gut, den Rückkehrcode undefiniert zu lassen. Übrigens getlineist eine GNU-Erweiterung - falls sich jemand wundert.
Maxschlepzig

@maxschlepzig: Wird der Speicher durch die von getline () zugewiesene Zeile angezeigt? Wird es dynamisch von malloc auf dem Heap oder statisch auf dem Stack zugewiesen? Sie sagten, es sei nicht notwendig, es freizugeben. Wird es also nicht dynamisch zugewiesen?
Tim

1
@Tim, ja, wenn Sie zB den Code so umgestalten, dass es sich um eine eigenständige Funktion handelt - etwa - f, die mehrmals von einem anderen Code aufgerufen wird, müssen Sie freenach dem letzten Aufruf von getlineam Ende dieser Funktion aufrufen f.
Maxschlepzig

6

Für eine Zeichenfolge wäre die einfachste mit trund wc(kein Overkill mit awkoder sed) - aber beachten Sie die obigen Kommentare zu tr, zählt Bytes, nicht Zeichen -

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

Dabei $xhandelt es sich um die Variable, die die auszuwertende Zeichenfolge (keine Datei) enthält.


4

Hier ist eine andere C-Lösung, die nur STD C und weniger Speicher benötigt:

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

Dies wird in der letzten Zeile nicht gemeldet, wenn kein
abschließendes

1
@fred, ja, das ist absichtlich so, denn eine Zeile ohne Schlepp \nist keine echte Zeile. Dies ist das gleiche Verhalten wie bei meiner anderen Antwort sed / awk (tr / awk).
Maxschlepzig

3

Wir können grepmit verwenden regex, um es einfacher und leistungsfähiger zu machen.

Um ein bestimmtes Zeichen zu zählen.

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

Zum Zählen von Sonderzeichen einschließlich Leerzeichen.

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

Hier wählen wir ein beliebiges Zeichen mit [\S\s]und mit der -oOption, dass wir grepjede Übereinstimmung (dh jedes Zeichen) in einer separaten Zeile ausgeben. Und dann verwenden, wc -lum jede Zeile zu zählen.


OP möchte nicht die Anzahl aller Zeichen in einer Datei drucken! Er möchte die Nummer eines bestimmten Zeichens zählen / drucken. zum Beispiel, wie viele "in jeder Zeile sind; und für alle anderen Zeichen. siehe seine frage und auch akzeptierte antwort.
α 23sнιη

3

Vielleicht wäre eine einfachere, rein unkomplizierte Antwort die Verwendung von Split. Split nimmt einen String und verwandelt ihn in ein Array. Der Rückgabewert ist die Anzahl der generierten Array-Elemente + 1.

Der folgende Code gibt die Anzahl der Ausdrucke aus "wird in jeder Zeile angezeigt.

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

Weitere Informationen zu split http://www.staff.science.uu.nl/~oostr102/docs/nawk/nawk_92.html


2

Hier ist ein einfaches Python-Skript, mit dem Sie die Anzahl "in jeder Zeile einer Datei ermitteln können:

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

Hier haben wir die countMethode des eingebauten strTyps verwendet.


2

Für eine reine Bash-Lösung (jedoch bash-spezifisch): Wenn $xdie Variable Ihren String enthält:

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

Das ${x//Ding entfernt alle Zeichen außer ", ${#x2}berechnet die Länge dieser Pause.

(Ursprünglicher Vorschlag mit exprdem es Probleme gibt, siehe Kommentare:)

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

Beachten Sie, dass es GNU-spezifisch ist exprund Bytes zählt, keine Zeichen. Mit anderen expr:expr "x${x...}" : "x.*" - 1
Stéphane Chazelas

Oh richtig, danke! Ich habe es mit einer anderen Idee modifiziert, die ich gerade hatte, was den Vorteil hat, überhaupt kein externes Programm zu verwenden.
Marian,

2

Ersetzen Sie adurch das zu zählende Zeichen. Ausgabe ist der Zähler für jede Zeile.

perl -nE 'say y!a!!'

2

Zeitvergleich der vorgestellten Lösungen (keine Antwort)

Die Effizienz der Antworten ist nicht wichtig. Trotzdem habe ich nach dem @ josephwb-Ansatz versucht, alle vorgestellten Antworten zeitlich abzustimmen.

Als Eingabe verwende ich die portugiesische Übersetzung von Victor Hugo "Les Miserables" (großes Buch!) Und zähle die Vorkommen von "a". Meine Ausgabe hat 5 Bände, viele Seiten ...

$ wc miseraveis.txt 
29331  304166 1852674 miseraveis.txt 

C-Antworten wurden mit gcc zusammengestellt (keine Optimierungen).

Jede Antwort wurde dreimal ausgeführt und die beste ausgewählt.

Vertraue diesen Zahlen nicht zu sehr (meine Maschine erledigt andere Aufgaben usw. usw.). Ich teile diese Zeiten mit Ihnen, weil ich einige unerwartete Ergebnisse habe und ich bin sicher, dass Sie noch mehr finden werden ...

  • 14 von 16 zeitgesteuerten Lösungen dauerten weniger als 1 Sekunde. 9 weniger als 0,1s, viele von ihnen mit Rohren
  • 2 Lösungen, die bash Zeile für Zeile verwenden, die 30.000 Zeilen verarbeiten, indem neue Prozesse erstellt werden. Berechnen Sie die richtige Lösung in 10s / 20s.
  • grep -oP aist Baum mal schneller als grep -o a (10; 11 vs 12)
  • Der Unterschied zwischen C und anderen ist nicht so groß, wie ich erwartet hatte. (7; 8 vs 2; 3)
  • (Schlussfolgerungen erwünscht)

(ergibt eine zufällige Reihenfolge)

=========================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

1
grep -n -o \" file | sort -n | uniq -c | cut -d : -f 1

wo grep all das schwere heben macht: meldet jedes Zeichen, das in jeder Zeilennummer gefunden wurde. Der Rest besteht nur darin, die Anzahl pro Zeile zu summieren und die Ausgabe zu formatieren.

Entfernen Sie die -nund erhalten Sie die Zählung für die gesamte Datei.

Das Zählen einer 1,5-Megabyte-Textdatei in weniger als 0,015 Sekunden scheint schnell zu sein.
Und arbeitet mit Zeichen (nicht Bytes).


1

Eine Lösung für Bash. Kein externes Programm aufgerufen (schneller für kurze Zeichenfolgen).

Wenn sich der Wert in einer Variablen befindet:

$ a='"Hello!"'

Dies gibt aus, wie viele "es enthält:

$ b="${a//[^\"]}"; echo "${#b}"
2
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.