So drucken Sie die Anzahl der Zeichen in jeder Zeile einer Textdatei


81

Ich möchte die Anzahl der Zeichen in jeder Zeile einer Textdatei mit einem Unix-Befehl drucken. Ich weiß, dass es mit Powershell einfach ist

gc abc.txt | % {$_.length}

aber ich brauche Unix-Befehl.

Antworten:


151

Verwenden Sie Awk.

awk '{ print length }' abc.txt

2
Dies ist mehrere Größenordnungen schneller als das Anwenden von wc -c auf jede Zeile!
Aerijman

@aerijman Für diese Art von Problemen ist die Anzahl der Prozesserstellungen normalerweise der größte Leistungsunterschied.
MarcH

Wenn eine Zeile in der Datei Emojis enthält, wird nicht die erwartete Länge erzeugt.
user5507535

@ user5507535, es hängt davon ab, welche "Länge" Sie tatsächlich erwarten. Es gibt viele mögliche Definitionen für Unicode (mawk verwendet Bytes, hat gawk nicht überprüft).
Jan Hudec

16
while IFS= read -r line; do echo ${#line}; done < abc.txt

Es ist POSIX, also sollte es überall funktionieren.

Bearbeiten: -r hinzugefügt, wie von William vorgeschlagen.

Bearbeiten: Vorsicht vor Unicode-Handhabung. Bash und zsh mit korrekt eingestelltem Gebietsschema zeigen die Anzahl der Codepunkte an, während Bindestrich Bytes anzeigt. Sie müssen also überprüfen, was Ihre Shell tut. Und dann gibt es in Unicode sowieso viele andere mögliche Definitionen der Länge, also hängt es davon ab, was Sie tatsächlich wollen.

Bearbeiten: Präfix mit IFS=, um zu vermeiden, dass führende und nachfolgende Leerzeichen verloren gehen.


+1, aber ... dies schlägt fehl, wenn die Eingabe '\' enthält. Verwenden Sie read -r
William Pursell

Wenn eine Zeile in der Datei Emojis enthält, wird nicht die erwartete Länge erzeugt.
user5507535

@ user5507535, eigentlich hängt es davon ab, welche "Länge" Sie erwarten. Es gibt viele mögliche Definitionen für Unicode (aber in diesem Fall bewirken unterschiedliche Shells tatsächlich unterschiedliche Funktionen).
Jan Hudec

Setzen Sie immer IFS=den readBefehl, wenn Sie beliebige Daten einlesen möchten. Also IFS= read -r. readverwendet das IFS, um das Teilen von Wörtern durchzuführen, und obwohl alle geteilten Wörter dann wieder in die eine verfügbare Variable ( line) eingefügt werden, gibt es keine Garantie dafür, dass sie wieder zusammen mit allen ursprünglichen Trennzeichen eingefügt werden, die sie hatten, oder nur einem potenziell unterschiedlichen Einsen. Zum Beispiel mit dem Standard - IFS, die Linie foo barkönnte werden foo bar, verliert 7 Räume. (Zum Beispiel, wie der Stapelüberlauf die benachbarten Leerzeichen in dieser Beispielzeichenfolge in diesem Kommentar verloren hat).
mtraceur

@mtraceur, in der Dokumentation heißt es ausdrücklich, dass "verbleibende Wörter und ihre dazwischen liegenden Trennzeichen dem Nachnamen zugewiesen werden", sodass sie wieder zusammen mit dem ursprünglichen Trennzeichen eingefügt werden. Dies kümmert sich jedoch nicht um die führenden und nachfolgenden Begrenzer, die tatsächlich verloren gehen. Sie haben also Recht, IFSsollten eingestellt werden, aber das Problem, wenn es nicht ist, ist subtiler.
Jan Hudec

4

Ich habe die anderen oben aufgeführten Antworten ausprobiert, aber sie sind bei großen Dateien alles andere als anständige Lösungen - insbesondere, wenn die Größe einer einzelnen Zeile mehr als ~ 1/4 des verfügbaren Arbeitsspeichers belegt.

Sowohl bash als auch awk schlürfen die gesamte Linie, obwohl es für dieses Problem nicht benötigt wird. Bash tritt aus, sobald eine Zeile zu lang ist, auch wenn Sie über genügend Speicher verfügen.

Ich habe ein extrem einfaches, ziemlich unoptimiertes Python-Skript implementiert, das beim Testen mit großen Dateien (~ 4 GB pro Zeile) nicht schlürft und bei weitem eine bessere Lösung ist als die angegebenen.

Wenn dies zeitkritischer Code für die Produktion ist, können Sie die Ideen in C umschreiben oder bessere Optimierungen für den Leseaufruf durchführen (anstatt jeweils nur ein Byte zu lesen), nachdem Sie getestet haben, dass dies tatsächlich ein Engpass ist.

Code geht davon aus, dass newline ein Zeilenvorschubzeichen ist, was eine gute Annahme für Unix, aber YMMV unter Mac OS / Windows ist. Stellen Sie sicher, dass die Datei mit einem Zeilenvorschub endet, um sicherzustellen, dass die Anzahl der letzten Zeilenzeichen nicht übersehen wird.

from sys import stdin, exit

counter = 0
while True:
    byte = stdin.buffer.read(1)
    counter += 1
    if not byte:
        exit()
    if byte == b'\x0a':
        print(counter-1)
        counter = 0

1
Die Frage war für eine "Text" -Datei. Ich denke nicht, dass 4 GB pro Zeile zu einer vernünftigen Definition einer Textdatei passen.
MarcH

3

Hier ist ein Beispiel mit xargs:

$ xargs -d '\n' -I% sh -c 'echo % | wc -c' < file

Dieses "echo%" behandelt keine unsicheren Zeichen, die aus der Shell zitiert werden müssen. Außerdem teilt "xargs" Ihre Datei nach Leerzeichen und Zeilenumbrüchen auf, nicht nur nach Zeilenumbrüchen, wie vom Originalposter angefordert.
Rinder

1

Versuche dies:

while read line    
do    
    echo -e |wc -m      
done <abc.txt    

Du hast gemeint echo -e | wc -m, nicht wahr? Es ist nutzlos, Befehle zu verwenden. Shell kann Zeichen in einer Variablen zählen. Plus echo -eist völlig inkompatibel und funktioniert in der Hälfte der Shells, während das Beginnen mit einer Escape-Sequenz in einer anderen und nichts in den anderen funktioniert.
Jan Hudec
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.