Zählen Sie Zeilen in großen Dateien

71

Ich arbeite normalerweise mit Textdateien mit einer Größe von ~ 20 GB und zähle sehr oft die Anzahl der Zeilen in einer bestimmten Datei.

So wie ich es jetzt mache, ist es einfach cat fname | wc -lund es dauert sehr lange. Gibt es eine Lösung, die viel schneller wäre?

Ich arbeite in einem Hochleistungscluster mit installiertem Hadoop. Ich habe mich gefragt, ob ein Ansatz zur Kartenreduzierung helfen könnte.

Ich möchte, dass die Lösung so einfach wie ein Zeilenlauf ist, wie die wc -lLösung, aber nicht sicher, wie machbar sie ist.

Irgendwelche Ideen?

Dnaiel
quelle
Hat jeder der Knoten bereits eine Kopie der Datei?
Ignacio Vazquez-Abrams
Vielen Dank. Ja. Um auf viele Knoten zuzugreifen, verwende ich ein LSF-System, das manchmal eine ziemlich nervige Wartezeit aufweist. Deshalb wäre die ideale Lösung, Hadoop / Mapreduce in einem Knoten zu verwenden, aber es wäre möglich, andere Knoten zu verwenden (und dann die Wartezeit hinzuzufügen) kann es langsamer machen als nur die Katze wc Ansatz)
Dnaiel
3
wc -l fnamekann schneller sein. Sie können auch versuchen, vim -R fnameob dies schneller ist (es sollte Ihnen die Anzahl der Zeilen nach dem Start anzeigen).
ott--
1
Sie können es mit einem Schweineskript tun, siehe meine Antwort hier: stackoverflow.com/questions/9900761/…
Arnon Rotem-Gal-Oz
Etwas schneller ist es, sich an die nutzlose Verwendung der Katzenregel zu erinnern .
Arielf

Antworten:

106

Versuchen: sed -n '$=' filename

Auch Katze ist unnötig: wc -l filenamereicht auf Ihre derzeitige Weise aus.

PP
quelle
mmm interessant. Würde ein Map / Reduce-Ansatz helfen? Ich nehme an, wenn ich alle Dateien in einem HDFS-Format speichere und dann versuche, die Linien mit map / redu zu zählen, wäre das viel schneller, oder?
Dnaiel
@ lvella. Es kommt darauf an, wie sie umgesetzt werden. Nach meiner Erfahrung ist sedes schneller gegangen. Vielleicht kann ein kleines Benchmarking helfen, es besser zu verstehen.
PP
@ KingsIndian. Indeeed, habe gerade sed ausprobiert und es war 3-fach schneller als wc in einer 3Gb-Datei. Danke KingsIndian.
Dnaiel
32
@Dnaiel Wenn ich vermuten würde, dass Sie wc -l filenamezuerst ausgeführt haben, dann sind Sie ausgeführt worden sed -n '$=' filename, sodass wc beim ersten Durchlauf die gesamte Datei von der Festplatte lesen musste, damit sie vollständig auf Ihrem wahrscheinlich größer als 3 GB großen Speicher zwischengespeichert werden kann sedkönnte gleich viel schneller laufen. Ich habe die Tests selbst mit einer 4-GB-Datei auf einem Computer mit 6-GB-RAM durchgeführt, aber ich habe sichergestellt, dass sich die Datei bereits im Cache befindet. die Punktzahl: sed- 0m12.539s, wc -l- 0m1.911s. wcWar also 6,56 mal schneller. Das Experiment wurde wiederholt, aber der Cache vor jedem Lauf geleert. Beide dauerten ungefähr 58 Sekunden.
lvella
1
Diese Lösung mit sed hat den zusätzlichen Vorteil, dass kein Zeilenendezeichen erforderlich ist. wc zählt die Zeilenendezeichen ("\ n"). Wenn Sie also beispielsweise eine Zeile in der Datei ohne \ n haben, gibt wc 0 zurück. sed gibt korrekt 1 zurück.
SevakPrime
14

Ihr begrenzender Geschwindigkeitsfaktor ist die E / A-Geschwindigkeit Ihres Speichergeräts. Ein Wechsel zwischen einfachen Zeilenumbrüchen / Musterzählprogrammen hilft daher nicht weiter, da der Unterschied in der Ausführungsgeschwindigkeit zwischen diesen Programmen wahrscheinlich durch die Art und Weise unterdrückt wird, in der die Festplatte / der Speicher langsamer ist. was auch immer du hast.

Wenn Sie jedoch dieselbe Datei auf Festplatten / Geräte kopiert haben oder die Datei auf diese Festplatten verteilt ist, können Sie den Vorgang auf jeden Fall parallel ausführen. Ich weiß nicht genau über diesen Hadoop Bescheid, aber vorausgesetzt, Sie können die Datei mit 10 GB von 4 verschiedenen Speicherorten aus lesen, können Sie 4 verschiedene Zeilenzählprozesse ausführen, jeder in einem Teil der Datei, und ihre Ergebnisse zusammenfassen:

$ dd bs=4k count=655360 if=/path/to/copy/on/disk/1/file | wc -l &
$ dd bs=4k skip=655360 count=655360 if=/path/to/copy/on/disk/2/file | wc -l &
$ dd bs=4k skip=1310720 count=655360 if=/path/to/copy/on/disk/3/file | wc -l &
$ dd bs=4k skip=1966080 if=/path/to/copy/on/disk/4/file | wc -l &

Beachten Sie das &an jeder Befehlszeile, damit alle parallel ausgeführt werden. ddfunktioniert wie cathier, aber lassen Sie uns angeben, wie viele Bytes gelesen werden sollen ( count * bsBytes) und wie viele am Anfang der Eingabe skip * bsübersprungen werden sollen ( Bytes). Es funktioniert in Blöcken, daher muss angegeben werdenbs die Blockgröße angegeben werden. In diesem Beispiel habe ich die 10-GB-Datei in 4 gleiche Blöcke von 4 KB * 655360 = 2684354560 Byte = 2,5 GB partitioniert, eine für jeden Auftrag. Möglicherweise möchten Sie ein Skript einrichten, das dies basierend auf der Größe der Datei für Sie erledigt Datei und die Anzahl der parallelen Jobs, die Sie ausführen werden. Sie müssen auch das Ergebnis der Ausführungen zusammenfassen, was ich wegen meines Mangels an Shell-Skript-Fähigkeiten nicht getan habe.

Wenn Ihr Dateisystem intelligent genug ist, um große Dateien auf viele Geräte wie ein RAID oder ein verteiltes Dateisystem oder ähnliches aufzuteilen und E / A-Anforderungen, die parallelisiert werden können, automatisch zu parallelisieren, können Sie eine solche Aufteilung durchführen, indem Sie viele parallele Jobs ausführen, aber verwenden der gleiche Dateipfad, und Sie können immer noch einen gewissen Geschwindigkeitsgewinn haben.

BEARBEITEN: Eine andere Idee, die mir gekommen ist, ist, wenn die Zeilen in der Datei dieselbe Größe haben, können Sie die genaue Anzahl der Zeilen erhalten, indem Sie die Größe der Datei durch die Größe der Zeile dividieren, beide in Bytes. Sie können dies fast augenblicklich in einem einzigen Job tun. Wenn Sie die mittlere Größe haben und sich nicht genau um die Zeilenanzahl kümmern, aber eine Schätzung wünschen, können Sie dieselbe Operation ausführen und ein zufriedenstellendes Ergebnis viel schneller als die exakte Operation erzielen.

lvella
quelle
8

Verwenden Sie auf einem Multi-Core-Server GNU parallel, um Dateizeilen parallel zu zählen. Nachdem die Zeilenanzahl jeder Datei gedruckt wurde, summiert bc alle Zeilenzahlen.

find . -name '*.txt' | parallel 'wc -l {}' 2>/dev/null | paste -sd+ - | bc

Um Platz zu sparen, können Sie sogar alle Dateien komprimieren. In der folgenden Zeile wird jede Datei dekomprimiert und ihre Zeilen parallel gezählt. Anschließend werden alle Zählungen summiert.

find . -name '*.xz' | parallel 'xzcat {} | wc -l' 2>/dev/null | paste -sd+ - | bc
Nicholas Sushkin
quelle
Gute Idee. Ich benutze das. Siehe meine Antwort zur Verwendung von ddstatt wczum Lesen der Datei, wenn ein Festplattenengpass ein Problem darstellt.
Sudo
8

Gemäß meinem Test kann ich überprüfen, ob die Spark-Shell (basierend auf Scala) viel schneller ist als die anderen Tools (GREP, SED, AWK, PERL, WC). Hier ist das Ergebnis des Tests, den ich für eine Datei mit 23782409 Zeilen ausgeführt habe

time grep -c $ my_file.txt;

real 0m44.96s Benutzer 0m41.59s sys 0m3.09s

time wc -l my_file.txt;

real 0m37.57s Benutzer 0m33.48s sys 0m3.97s

time sed -n '$=' my_file.txt;

real 0m38.22s Benutzer 0m28.05s sys 0m10.14s

time perl -ne 'END { $_=$.;if(!/^[0-9]+$/){$_=0;};print "$_" }' my_file.txt;;

real 0m23.38s Benutzer 0m20.19s sys 0m3.11s

time awk 'END { print NR }' my_file.txt;

real 0m19.90s Benutzer 0m16.76s sys 0m3.12s

spark-shell
import org.joda.time._
val t_start = DateTime.now()
sc.textFile("file://my_file.txt").count()
val t_end = DateTime.now()
new Period(t_start, t_end).toStandardSeconds()

res1: org.joda.time.Seconds = PT15S

Pramod Tiwari
quelle
Sie können Ihrem Befehl einfach ein Präfix voranstellen time, um die Laufzeit zu erhalten.
Javad
Ich habe gerade festgestellt, dass ich ein AIX-basiertes System hatte, auf dem ich diese Tests durchgeführt habe, und es unterstützt das Zeitschlüsselwort nicht so, wie ich es erwartet hatte
Pramod Tiwari
FWIW, ich glaube nicht, dass Sie sich darauf verlassen können, dass diese Zeiten über alle Betriebssysteme hinweg konsistent sind. "Wc -l" war für mich schneller als awk, da ich Zeilen in einer 1,1-GB-Protokolldatei gezählt habe. Sed war allerdings langsam. Vielen Dank, dass Sie die Optionen gezeigt haben!
Peter Turner
Ich stimme völlig mit Ihnen. Es würde sicherlich sehr stark von der Optimierung dieses Dienstprogramms auf verschiedenen Betriebssystemen abhängen. Ich bin mir nicht sicher, wie diese kleinen Dienstprogramme in verschiedenen Geschmacksrichtungen gestaltet sind. Vielen Dank, dass Sie diese Perspektive eingebracht haben.
Pramod Tiwari
6

Wenn sich Ihre Daten in HDFS befinden, ist der schnellste Ansatz möglicherweise die Verwendung von Hadoop-Streaming. Die COUNT UDF von Apache Pig arbeitet mit einem Beutel und verwendet daher einen einzelnen Reduzierer, um die Anzahl der Zeilen zu berechnen. Stattdessen können Sie die Anzahl der Reduzierungen in einem einfachen Hadoop-Streaming-Skript wie folgt manuell festlegen:

$HADOOP_HOME/bin/hadoop jar $HADOOP_HOME/hadoop-streaming.jar -Dmapred.reduce.tasks=100 -input <input_path> -output <output_path> -mapper /bin/cat -reducer "wc -l"

Beachten Sie, dass ich die Anzahl der Reduzierstücke manuell auf 100 eingestellt habe, Sie diesen Parameter jedoch einstellen können. Sobald der Map-Reduction-Job abgeschlossen ist, wird das Ergebnis jedes Reduzierers in einer separaten Datei gespeichert. Die endgültige Anzahl der Zeilen ist die Summe der von allen Reduzierern zurückgegebenen Zahlen. Sie können die endgültige Anzahl der Zeilen wie folgt erhalten:

$HADOOP_HOME/bin/hadoop fs -cat <output_path>/* | paste -sd+ | bc
Pirooz
quelle
4

Ich weiß, dass die Frage jetzt ein paar Jahre alt ist, aber dieses Bash-Skript erweitert Ivellas letzte Idee und schätzt die Zeilenanzahl einer großen Datei innerhalb von Sekunden oder weniger, indem es die Größe einer Zeile misst und daraus extrapoliert:

#!/bin/bash
head -2 $1 | tail -1 > $1_oneline
filesize=$(du -b $1 | cut -f -1)
linesize=$(du -b $1_oneline | cut -f -1)
rm $1_oneline
echo $(expr $filesize / $linesize)

Wenn Sie dieses Skript lines.shbenennen, können Sie aufrufen lines.sh bigfile.txt, um die geschätzte Anzahl der Zeilen abzurufen. In meinem Fall (ca. 6 GB, Export aus der Datenbank) betrug die Abweichung von der tatsächlichen Zeilenanzahl nur 3%, lief jedoch ca. 1000-mal schneller. Übrigens habe ich die zweite, nicht die erste Zeile als Basis verwendet, da die erste Zeile Spaltennamen hatte und die tatsächlichen Daten in der zweiten Zeile begannen.

Nico
quelle
Für vor allem Antworten habe ich mit (i) cat filename | versucht wc -l # gibt mir eine falsche Antwort (ii) sed -n '$ =' Dateiname # gibt mir ein falsches Ergebnis. Dann habe ich es mit diesem Skript versucht und mir ein korrektes Ergebnis von ungefähr 1 Million Zeilen gegeben. Danke +1
Sanket Thakkar
Man konnte eigentlich nicht den Kopf sondern den Schwanz in der ersten Zeile machen. Und warum 1, nimm 1000 und multipliziere es am Ende zurück. Wenn die Zeilen mehr oder weniger zufällig sind, erhalten Sie ein genaueres Ergebnis als bei Verwendung von 1 Zeilenberechnung. Das Problem besteht darin, dass das Recordset schlecht verteilt ist. Dann ist diese Zahl nichts wert :(
Алексей Лещук
3

Hadoop bietet im Wesentlichen einen Mechanismus, um etwas Ähnliches auszuführen, wie es @Ivella vorschlägt.

Das HDFS (Distributed File System) von Hadoop nimmt Ihre 20-GB-Datei und speichert sie im gesamten Cluster in Blöcken fester Größe. Nehmen wir an, Sie konfigurieren die Blockgröße auf 128 MB. Die Datei wird in Blöcke von 20 x 8 x 128 MB aufgeteilt.

Sie würden dann ein Kartenreduzierungsprogramm über diese Daten ausführen, im Wesentlichen die Zeilen für jeden Block (in der Kartenphase) zählen und dann diese Blockzeilenzahlen zu einer endgültigen Zeilenanzahl für die gesamte Datei reduzieren.

Was die Leistung betrifft, ist im Allgemeinen die Leistung umso besser, je größer Ihr Cluster ist (mehr WC laufen parallel über unabhängigere Festplatten). Die Job-Orchestrierung ist jedoch mit einem gewissen Aufwand verbunden, der bedeutet, dass die Ausführung des Jobs auf kleineren Dateien nicht schneller erfolgt Durchsatz als ein lokales WC ausführen

Chris White
quelle
2

Ich bin mir nicht sicher, ob Python schneller ist:

[root@myserver scripts]# time python -c "print len(open('mybigfile.txt').read().split('\n'))"

644306


real    0m0.310s
user    0m0.176s
sys     0m0.132s

[root@myserver scripts]# time  cat mybigfile.txt  | wc -l

644305


real    0m0.048s
user    0m0.017s
sys     0m0.074s
Eugen
quelle
Sie zeigen tatsächlich, dass Python hier langsamer ist.
Arnaud Potier
1
Python könnte den Job machen, aber sicher nicht mit ...read().split("\n"). Ändern Sie das für sum(1 for line in open("mybigfile.txt")) und Sie haben einen besseren naiven Ansatz (ich nehme keinen Vorteil aus dem HDFS-Setup)
jsbueno
2

Wenn Ihr Engpass die Festplatte ist, ist es wichtig, wie Sie daraus lesen. dd if=filename bs=128M | wc -list viel schneller als wc -l filenameoder cat filename | wc -lfür meinen Computer mit Festplatte und schneller CPU und RAM. Sie können mit der Blockgröße herumspielen und sehen, welche ddBerichte als Durchsatz angezeigt werden. Ich drehte es auf 1GiB.

Hinweis: Es gibt einige Debatten darüber, ob catoderdd es schneller ist . Ich behaupte nur, dass ddes je nach System schneller gehen kann und dass es für mich ist. Probieren Sie es aus.

sudo
quelle
1

Wenn Ihr Computer über Python verfügt, können Sie dies über die Shell versuchen:

python -c "print len(open('test.txt').read().split('\n'))"

Dies wird verwendet python -c, um einen Befehl zu übergeben, der im Grunde die Datei liest und durch die "neue Zeile" aufteilt, um die Anzahl der Zeilenumbrüche oder die Gesamtlänge der Datei zu erhalten.

@ BlueMoon's :

bash-3.2$ sed -n '$=' test.txt
519

Verwenden Sie die oben genannten:

bash-3.2$ python -c "print len(open('test.txt').read().split('\n'))"
519
ZenOfPython
quelle
7
Python-Analyse für jedes \ n in einer 20-GB-Datei zu haben, scheint eine ziemlich schrecklich langsame Möglichkeit zu sein, dies zu versuchen.
Mikeschuld
1
Schreckliche Lösung im Vergleich zur Verwendung von sed.
PureW
1
Das Problem ist nicht, dass Python das "\ n" analysiert - sowohl sed als auch wc müssen dies ebenfalls tun. Was daran schrecklich ist, ist, alles in den Speicher zu lesen und Python zu bitten, den Datenblock bei jedem "\ n" aufzuteilen (nicht nur alle Daten im Speicher zu duplizieren, sondern auch eine relativ teure Objekterstellung für jede Zeile
durchzuführen
python -c "print(sum(1 for line in open('text.txt'))"wäre eine bessere Lösung in Python, da nicht die gesamte Datei in den Speicher eingelesen wird, sondern entweder sed oder wc eine viel bessere Lösung wäre.
Zombieguru
1
find  -type f -name  "filepattern_2015_07_*.txt" -exec ls -1 {} \; | cat | awk '//{ print $0 , system("cat " $0 "|" "wc -l")}'

Ausgabe:

Ceaser Ashton-Bradley Junior
quelle
0

Lasst uns annehmen:

  • Ihr Dateisystem ist verteilt
  • Ihr Dateisystem kann problemlos die Netzwerkverbindung zu einem einzelnen Knoten herstellen
  • Sie greifen wie normale Dateien auf Ihre Dateien zu

Dann möchten Sie die Dateien wirklich in Teile zerlegen, Teile auf mehreren Knoten parallel zählen und die Ergebnisse von dort zusammenfassen (dies ist im Grunde die Idee von @Chris White).

So machen Sie das mit GNU Parallel (Version> 20161222). Sie müssen die Knoten auflisten ~/.parallel/my_cluster_hostsund sshauf alle zugreifen können:

parwc() {
    # Usage:
    #   parwc -l file                                                                

    # Give one chunck per host                                                     
    chunks=$(cat ~/.parallel/my_cluster_hosts|wc -l)
    # Build commands that take a chunk each and do 'wc' on that                    
    # ("map")                                                                      
    parallel -j $chunks --block -1 --pipepart -a "$2" -vv --dryrun wc "$1" |
        # For each command                                                         
        #   log into a cluster host                                                
        #   cd to current working dir                                              
        #   execute the command                                                    
        parallel -j0 --slf my_cluster_hosts --wd . |
        # Sum up the number of lines                                               
        # ("reduce")                                                               
        perl -ne '$sum += $_; END { print $sum,"\n" }'
}

Benutzen als:

parwc -l myfile
parwc -w myfile
parwc -c myfile
Ole Tange
quelle
Benötigen Sie nicht die Zeilenanzahl der Originaldatei, um zu entscheiden, wie sie partitioniert werden soll?
Alex Reynolds
Nein, es ist nach Bytes unterteilt - nicht nach Zeilen.
Ole Tange
0

Ich habe eine Textdatei mit 645 GB, und keine der früheren exakten Lösungen (z. B. wc -l) hat innerhalb von 5 Minuten eine Antwort zurückgegeben.

Stattdessen gibt es hier ein Python-Skript, das die ungefähre Anzahl von Zeilen in einer großen Datei berechnet . (Meine Textdatei enthält anscheinend ungefähr 5,5 Milliarden Zeilen.) Das Python-Skript führt Folgendes aus:

A. Zählt die Anzahl der Bytes in der Datei.

B. Liest die ersten NZeilen in der Datei (als Beispiel) und berechnet die durchschnittliche Zeilenlänge.

C. Berechnet A / B als ungefähre Anzahl von Zeilen.

Es folgt der Linie von Nicos Antwort , berechnet aber nicht die Länge einer Zeile, sondern die durchschnittliche Länge der ersten NZeilen.

Hinweis: Ich gehe von einer ASCII-Textdatei aus, daher erwarte ich, dass die Python- len()Funktion die Anzahl der Zeichen als Anzahl der Bytes zurückgibt.

Fügen Sie diesen Code in eine Datei ein line_length.py:

#!/usr/bin/env python

# Usage:
# python line_length.py <filename> <N> 

import os
import sys
import numpy as np

if __name__ == '__main__':

    file_name = sys.argv[1]
    N = int(sys.argv[2]) # Number of first lines to use as sample.
    file_length_in_bytes = os.path.getsize(file_name)
    lengths = [] # Accumulate line lengths.
    num_lines = 0

    with open(file_name) as f:
        for line in f:
            num_lines += 1
            if num_lines > N:
                break
            lengths.append(len(line))

    arr = np.array(lengths)
    lines_count = len(arr)
    line_length_mean = np.mean(arr)
    line_length_std = np.std(arr)

    line_count_mean = file_length_in_bytes / line_length_mean

    print('File has %d bytes.' % (file_length_in_bytes))
    print('%.2f mean bytes per line (%.2f std)' % (line_length_mean, line_length_std))
    print('Approximately %d lines' % (line_count_mean))

Rufen Sie es so mit N= 5000 auf.

% python line_length.py big_file.txt 5000

File has 645620992933 bytes.
116.34 mean bytes per line (42.11 std)
Approximately 5549547119 lines

Die Datei enthält also ungefähr 5,5 Milliarden Zeilen.

stackoverflowuser2010
quelle