Licz linie w dużych plikach

Zwykle pracuję z plikami tekstowymi o rozmiarze ~20 Gb i bardzo często zliczam liczbę linii w danym pliku.

Sposób, w jaki teraz to robię, jest po prostu cat fname | wc -l i trwa to bardzo długo. Czy jest jakieś rozwiązanie, które byłoby znacznie szybsze?

Pracuję w klastrze o wysokiej wydajności z zainstalowanym Hadoopem. Zastanawiałem się, czy podejście do redukcji map mogłoby pomóc.

Chciałbym, aby rozwiązanie było tak proste jak jedna linia, jak rozwiązanie wc -l, ale nie wiem, jak to wykonalne jest.

Jakieś pomysły?
Author: codeforester, 2012-10-04

12 answers

Try: sed -n '$=' filename

Również kot jest niepotrzebny: wc -l filename Wystarczy w obecny sposób.

 82
Author: P.P.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-03 20:45:39

Twój limitujący Współczynnik prędkości jest prędkością We/Wy urządzenia pamięci masowej, więc zmiana pomiędzy prostymi znakami nowej linii/programami zliczającymi wzorce nie pomoże, ponieważ różnica prędkości wykonania między tymi programami może być tłumiona przez sposób wolniejszy Dysk/Pamięć / cokolwiek masz.

Ale jeśli masz ten sam plik skopiowany na dyski/urządzenia lub plik jest rozprowadzany między tymi dyskami, możesz z pewnością wykonać operację równolegle. Nie wiem dokładnie o tym Hadoop, ale zakładając, że możesz odczytać plik 10gb z 4 różnych lokalizacji, możesz uruchomić 4 różne procesy liczenia linii, każdy w jednej części pliku i zsumować ich wyniki:

$ 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 &

Zwróć uwagę na & w każdym wierszu poleceń, więc wszystkie będą działać równolegle; dd działa jak cat tutaj, ale pozwól nam określić, ile bajtów do odczytania (count * bs bajtów) i ile bajtów do pominięcia na początku wejścia (skip * bs bajtów). Działa w blokach, stąd potrzeba określenia bs jako bloku rozmiar. W tym przykładzie podzieliłem plik 10Gb na 4 równe kawałki 4Kb * 655360 = 2684354560 bajtów = 2.5 GB, po jednym dla każdego zadania, możesz ustawić skrypt, który zrobi to za Ciebie na podstawie rozmiaru pliku i liczby równoległych zadań, które będziesz uruchamiał. Musisz również podsumować wynik egzekucji, czego nie zrobiłem z powodu mojego braku zdolności skryptu powłoki.

Jeśli Twój system plików jest wystarczająco inteligentny, aby podzielić duży plik między wiele urządzeń, takich jak RAID lub rozproszony system plików lub coś, i automatycznie paralelizować żądania We / Wy, które mogą być sparalelizowane, można zrobić taki podział, uruchamiając wiele równoległych zadań, ale używając tej samej ścieżki pliku, i nadal może mieć pewien wzrost prędkości.

Edytuj: Innym pomysłem, który przyszło mi do głowy, jest to, że jeśli linie wewnątrz pliku mają ten sam rozmiar, można uzyskać dokładną liczbę linii, dzieląc Rozmiar Pliku przez rozmiar linii, zarówno w bajtach. Możesz to zrobić niemal natychmiast w jednej pracy. Jeśli masz średni rozmiar nie dbaj dokładnie o liczbę linii, ale chcesz oszacować, możesz wykonać tę samą operację i uzyskać zadowalający wynik znacznie szybciej niż dokładna operacja.

 11
Author: lvella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-13 19:40:13

Na serwerze wielordzeniowym użyj GNU parallel do liczenia linii plików równolegle. Po wydrukowaniu każdego z plików liczba wierszy, BC sumuje wszystkie liczby wierszy.

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

Aby zaoszczędzić miejsce, możesz nawet zachować wszystkie pliki skompresowane. Następująca linia rozpakowuje każdy plik i zlicza jego linie równolegle, a następnie sumuje wszystkie zliczenia.

find . -name '*.xz' | parallel 'xzcat {} | wc -l' 2>/dev/null | paste -sd+ - | bc
 8
Author: Nicholas Sushkin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-25 17:56:49

Jeśli Twoje dane znajdują się na HDFS, być może najszybszym rozwiązaniem jest użycie strumieniowania hadoop. Apache Pig ' s COUNT UDF, działa na worku i dlatego używa jednego reduktora do obliczania liczby wierszy. Zamiast tego można ręcznie ustawić liczbę reduktorów w prostym skrypcie strumieniowym hadoop w następujący sposób:

$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"

Zauważ, że ręcznie ustawiłem liczbę reduktorów na 100, ale możesz dostroić ten parametr. Po wykonaniu zadania map-reduce wynik z każdego reduktora jest przechowywany w osobnym plik. Ostateczna liczba wierszy jest sumą liczb zwracanych przez wszystkie reduktory. możesz uzyskać końcową liczbę wierszy w następujący sposób:

$HADOOP_HOME/bin/hadoop fs -cat <output_path>/* | paste -sd+ | bc
 6
Author: Pirooz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-27 21:56:00

Zgodnie z moim testem mogę sprawdzić, czy Spark-Shell (oparty na Scali) jest o wiele szybszy niż inne narzędzia (GREP, SED, AWK, PERL, WC). Oto wynik testu, który uruchomiłem na pliku, który miał 23782409 linii

time grep -c $ my_file.txt;
Real 0m44. 96s user 0m41. 59s sys 0m3. 09S
time wc -l my_file.txt;
Real 0m37. 57s user 0m33. 48s sys 0m3. 97s
time sed -n '$=' my_file.txt;
Real 0m38.22s user 0m28. 05s sys 0m10. 14s

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

Real 0m23.38s użytkownik 0m20. 19s sys 0m3. 11s
time awk 'END { print NR }' my_file.txt;
Real 0m19. 90s user 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.czas.Sekund = PT15S

 5
Author: Pramod Tiwari,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-11-25 14:28:20

Hadoop zasadniczo zapewnia mechanizm do wykonania czegoś podobnego do tego, co sugeruje @Ivella.

HDFS (Distributed file system) firmy Hadoop zabierze Twój plik 20GB i zapisze go w klastrze w blokach o stałym rozmiarze. Powiedzmy, że skonfigurujesz rozmiar bloku na 128MB, plik zostanie podzielony na bloki 20x8x128mb.

Następnie uruchomisz program Map reduce nad tymi danymi, zasadniczo licząc linie dla każdego bloku (w etapie map), a następnie redukcja tych liczeń linii bloków do końcowej liczenia linii dla całego pliku.

Jeśli chodzi o wydajność, ogólnie rzecz biorąc, im większy klaster, tym lepsza wydajność( więcej wc działa równolegle, na bardziej niezależnych dyskach), ale istnieje pewien narzut w orkiestracji zadań, który oznacza, że uruchomienie zadania na mniejszych plikach nie przyniesie szybszej przepustowości niż uruchomienie lokalnego wc

 3
Author: Chris White,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-04 01:20:39

Wiem, że pytanie ma już kilka lat, ale rozszerzając ostatni pomysł Ivelli , Ten skrypt Bashaszacunki liczba linii dużego pliku w ciągu kilku sekund lub mniej, mierząc rozmiar jednej linii i ekstrapolując z niej:

#!/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)

Jeśli nazwiesz ten skrypt lines.sh, możesz wywołać lines.sh bigfile.txt, aby uzyskać szacunkową liczbę linii. W moim przypadku (około 6 GB, eksport z bazy danych) odchylenie od rzeczywistej liczby linii było tylko 3%, ale działało około 1000 razy szybciej. Nawiasem mówiąc, użyłem drugiej, a nie pierwszej linii jako podstawy, ponieważ pierwsza linia miała nazwy kolumn, a rzeczywiste dane zaczynały się w drugiej linii.

 3
Author: Nico,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-27 07:35:34

Nie jestem pewien czy python jest szybszy:

[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
 2
Author: eugene,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-21 12:31:47

Jeśli twoim wąskim gardłem jest dysk, liczy się sposób odczytu z niego. dd if=filename bs=128M | wc -l jest lot szybszy niż wc -l filename lub cat filename | wc -l dla mojego komputera, który ma HDD i szybki procesor i RAM. Możesz pobawić się rozmiarem bloku i zobaczyć, co dd raportuje jako przepustowość. Podkręciłem go do 1GiB.

Uwaga: Istnieje dyskusja na temat tego, czy cat lub dd jest szybszy. Wszystko, co twierdzę, to to, że dd może być szybsze, w zależności od systemu, i że jest to dla mnie. Spróbuj sam.

 2
Author: sudo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-06 01:48:54

Jeśli Twój komputer ma Pythona, możesz spróbować tego z powłoki:

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

To używa python -c do przekazania polecenia, które jest w zasadzie odczytaniem pliku i podzieleniem przez "nową linię", aby uzyskać liczbę nowych linii lub całkowitą długość pliku.

@BlueMoon ' s :

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

Używając powyższego:

bash-3.2$ python -c "print len(open('test.txt').read().split('\n'))"
519
 1
Author: ZenOfPython,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-11 02:16:51

Find-type f-name " filepattern_2015_07_*.txt "- exec ls -1 {}\; / cat / awk ' / / {print $0, system ("cat" $0 " | "wc-l")} '

Wyjście:

 1
Author: Ceaser Ashton-Bradley Junior,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-30 13:19:31

Załóżmy:

  • Twój system plików jest dystrybuowany
  • Twój system plików może łatwo wypełnić połączenie sieciowe do pojedynczego węzła
  • masz dostęp do swoich plików jak normalne pliki

Następnie naprawdę chcesz pociąć pliki na części, liczyć części równolegle na wielu węzłach i podsumować wyniki stamtąd(to jest w zasadzie pomysł @Chris White).

Oto Jak to zrobić z GNU Parallel (wersja > 20161222). Musisz wymienić węzły w ~/.parallel/my_cluster_hosts i musisz mieć ssh dostęp do wszystkich z nich:

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" }'
}

Użyj jako:

parwc -l myfile
parwc -w myfile
parwc -c myfile
 0
Author: Ole Tange,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-19 08:19:47