Mam duży plik tekstowy (~ 50 Gb, gdy gz'ed). Plik zawiera 4*N
wiersze lub N
rekordy; to znaczy każdy rekord składa się z 4 linii. Chciałbym podzielić ten plik na 4 mniejsze pliki o rozmiarze około 25% pliku wejściowego. Jak mogę podzielić plik na granicy rekordów?
Naiwnym podejściem byłoby zcat file | wc -l
uzyskanie liczby wierszy, podzielenie tej liczby przez 4, a następnie użycie split -l <number> file
. Jest to jednak powtarzane dwukrotnie, a licznik wierszy jest wyjątkowo wolny (36 minut). Czy jest lepszy sposób?
To się zbliża, ale nie tego szukam. Zaakceptowana odpowiedź również liczy liczbę wierszy.
EDYTOWAĆ:
Plik zawiera dane sekwencjonowania w formacie fastq. Dwa rekordy wyglądają tak (anonimowe):
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTTTATGTTTTTAATTAATTCTGTTTCCTCAGATTGATGATGAAGTTxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFFFFFFFFFAFFFFF#FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF<AFFFFFFFFFFAFFFFFFFFFFFFFFFFFFF<FFFFFFFFFAFFFAFFAFFAFFFFFFFFAFFFFFFAAFFF<FAFAFFFFA
@NxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxGCGA+ATAGAGAG
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxCCCTCTGCTGGAACTGACACGCAGACATTCAGCGGCTCCGCCGCCxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
AAAAA#FFFFF7FFFFFFAFFFFA#F7FFFFFFFFF7FFFFFAF<FFFFFFFFFFFFFFAFFF.F.FFFFF.FAFFF.FFFFFFFFFFFFFF.)F.FFA))FFF7)F7F<.FFFF.FFF7FF<.FFA<7FA.<.7FF.FFFAFF
Pierwszy wiersz każdego rekordu zaczyna się od @
.
EDYCJA 2:
zcat file > /dev/null
zajmuje 31 minut.
EDYCJA 3:
Tylko pierwsza linia zaczyna się od @
. Żaden z pozostałych nigdy. Zobacz tutaj . Zapisy muszą pozostać w porządku. Nie można nic dodawać do wynikowego pliku.
zcat file > /dev/null
?@
a także, że na rekord przypadają 4 linie. Czy oba są absolutne? - i czy linie 2,3,4 mogą zaczynać się od@
? i czy w pliku jest jakiś niezapisany nagłówek linii stopki?Odpowiedzi:
Nie sądzę, żebyś mógł to zrobić - niezawodnie i nie tak, jak prosisz. Chodzi o to, że współczynnik kompresji archiwum prawdopodobnie nie będzie równomiernie rozłożony od głowy do ogona - algorytm kompresji będzie miał zastosowanie w niektórych częściach lepiej niż w innych. Tak to działa. I dlatego nie możesz brać pod uwagę podziału na rozmiar skompresowanego pliku.
Co więcej,
gzip
po prostu nie obsługuje przechowywania oryginalnego rozmiaru skompresowanych plików większych niż 4 GB - nie może tego obsłużyć. Nie możesz więc zapytać archiwum, aby uzyskać niezawodny rozmiar - bo to Cię oszuka.Czteroliniowa rzecz - to całkiem proste. 4-plikowa rzecz - po prostu nie wiem, jak można to zrobić niezawodnie i z równomierną dystrybucją bez uprzedniego rozpakowania archiwum, aby uzyskać jego nieskompresowany rozmiar. Nie sądzę, żebyś mógł, bo próbowałem.
Jednak to, co można zrobić, to ustawić maksymalny rozmiar dla plików wyjściowych split, i upewnij się, że te zawsze są łamane na bariery płytowych. Możesz to łatwo zrobić. Oto mały skrypt, który zrobi to poprzez rozpakowanie
gzip
archiwum i przesłanie zawartości przez kilka jawnychdd
buforów potoku z konkretnymicount=$rpt
argumentami, przed przekazaniem tegolz4
do dekompresji / ponownej kompresji każdego pliku w locie. Wrzuciłem także kilka małychtee
sztuczek na fajce, aby wydrukować ostatnie cztery wiersze dla każdego segmentu na stderr.To będzie trwać, dopóki nie obsłuży wszystkich danych wejściowych. Nie próbuje podzielić go na pewien procent - którego nie może uzyskać - ale dzieli go na maksymalną liczbę nieprzetworzonych bajtów na podział. Poza tym duża część twojego problemu polega na tym, że nie możesz uzyskać wiarygodnego rozmiaru w swoim archiwum, ponieważ jest ono zbyt duże - cokolwiek robisz, nie rób tego ponownie - spraw, aby podziały były mniejsze niż 4 gb , może. Przynajmniej ten mały skrypt pozwala to zrobić bez konieczności zapisywania nieskompresowanego bajtu na dysku.
Oto krótsza wersja pozbawiona zasadniczych elementów - nie dodaje się do wszystkich elementów raportu:
Robi te same rzeczy, co pierwsze, w większości nie ma nic więcej do powiedzenia na ten temat. Ponadto jest mniej bałaganu, więc może łatwiej jest zobaczyć, co się dzieje.
Chodzi
IFS=
o to, aby obsłużyć jednąread
linię na iterację. My,read
ponieważ potrzebujemy, aby nasza pętla zakończyła się, gdy zakończy się wejście. Zależy to od wielkości twojego rekordu - który w twoim przykładzie wynosi 354 bajtów na. Stworzyłemgzip
archiwum 4+ GB z losowymi danymi w celu przetestowania.Losowe dane otrzymano w ten sposób:
... ale może nie musisz się tym tak bardzo przejmować, ponieważ masz już wszystkie dane. Powrót do rozwiązania ...
Zasadniczo
pigz
- który wydaje się dekompresować nieco szybciej niż robi tozcat
- odpompowuje nieskompresowany strumień idd
buforuje dane wyjściowe do bloków zapisu o rozmiarze dokładnie wielokrotności 354 bajtów. Pętla będzie po każdej iteracji do testu, który wejście jest wciąż przybywających, co będzie potem co przed kolejnym nazywa się czytać bloki formowany specjalnie na wielokrotnością 354 bajtów - do synchronizacji z buforowania procesu - na czas. Będzie jeden krótki odczyt na każdą iterację z powodu początkowej - ale to nie ma znaczenia, ponieważ drukujemy to w naszym procesie kolekcjonowania.read
$line
printf
printf
lz4
dd
dd
read $line
lz4
Skonfigurowałem go tak, aby każda iteracja odczytywała około 1 GB nieskompresowanych danych i kompresowała ten strumień do około 650 Mb lub mniej więcej.
lz4
jest znacznie szybszy niż jakakolwiek inna przydatna metoda kompresji - dlatego wybrałem ją tutaj, ponieważ nie lubię czekać.xz
prawdopodobnie jednak wykonałby znacznie lepszą robotę przy kompresji. Jedną z rzeczylz4
jest to, że często może dekompresować przy prędkościach zbliżonych do pamięci RAM - co oznacza, że wiele razy można dekompresowaćlz4
archiwum tak szybko, jak i tak można by je zapisać do pamięci.Ten duży wykonuje kilka raportów dla każdej iteracji. Obie pętle wydrukują
dd
raport o liczbie przesłanych nieprzetworzonych bajtów oraz prędkości i tak dalej. Wielka pętla wypisze również ostatnie 4 wiersze danych wejściowych na cykl i liczbę bajtów dla tego samego, a następnie jedenls
z katalogu, do którego piszęlz4
archiwa. Oto kilka rund wyników:źródło
gzip -l
działa tylko dla nieskompresowanych plików <2GiB IIRC (zresztą coś mniejszego niż plik OP).Dzielenie plików na granicach rekordów jest w rzeczywistości bardzo łatwe, bez żadnego kodu:
Spowoduje to utworzenie plików wyjściowych o długości 10000 linii, z nazwami nazwa_wyjściowa_aa, nazwa_wyjściowa_ab, nazwa_wyjściowa_ac, ... Przy wejściach tak dużych jak twoje, da ci to wiele plików wyjściowych. Zamień na
10000
dowolną wielokrotność czterech, a pliki wyjściowe mogą być tak duże lub małe, jak chcesz. Niestety, podobnie jak w przypadku innych odpowiedzi, nie ma dobrego sposobu na zagwarantowanie, że uzyskasz pożądaną liczbę (w przybliżeniu) równych rozmiarów plików wyjściowych bez zgadywania na temat danych wejściowych. (Lub właściwie przepuszczając całośćwc
.) Jeśli twoje rekordy są w przybliżeniu jednakowej wielkości (lub przynajmniej mniej więcej równomiernie rozłożone), możesz spróbować oszacować tak:To powie ci skompresowany rozmiar pierwszych 1000 rekordów twojego pliku. Na tej podstawie możesz prawdopodobnie oszacować, ile wierszy w każdym pliku ma kończyć się czterema plikami. (Jeśli nie chcesz pozostawić zdegenerowanego piątego pliku, pamiętaj, aby nieco zwiększyć swoje oszacowanie, lub przygotuj się na przyczepienie piątego pliku do końca czwartego.)
Edycja: Oto jeszcze jedna sztuczka, zakładając, że chcesz skompresowane pliki wyjściowe:
Spowoduje to utworzenie wielu mniejszych plików, a następnie szybkie ich połączenie. (Może być konieczne dostosowanie parametru -l w zależności od długości linii w plikach.) Zakłada się, że masz stosunkowo najnowszą wersję jądra GNU (dla split --filter) i około 130% rozmiaru pliku wejściowego w wolne miejsce na dysku. Zamień gzip / zcat na pigz / unpigz, jeśli ich nie masz. Słyszałem, że niektóre biblioteki oprogramowania (Java?) Nie obsługują plików gzip połączonych w ten sposób, ale jak dotąd nie miałem z tym żadnych problemów. (pigz używa tej samej sztuczki do równoległego kompresji).
źródło
Z tego, co zbieram po sprawdzeniu sfery Google i dalszym testowaniu
.gz
pliku 7,8 GiB , wydaje się, że metadane rozmiaru oryginalnego nieskompresowanego pliku nie są dokładne (tj. Nieprawidłowe ) dla dużych.gz
plików (większych niż 4GiB (może 2GiB dla niektórych wersjegzip
).Re. mój test metadanych gzip:
Wygląda więc na to, że nie można określić rozmiaru nieskompresowanego bez jego faktycznego rozpakowania (co jest nieco szorstkie, delikatnie mówiąc!)
Tak czy inaczej, tutaj jest sposób na podzielenie nieskompresowanego pliku na granicy rekordu, gdzie każdy rekord zawiera 4 linie .
Wykorzystuje rozmiar pliku w bajtach (przez
stat
) iawk
zliczając bajty (nie znaków). Określa, czy zakończeniem linii jestLF
|CR
|CRLF
, ten skrypt obsługuje długość końca linii za pomocą wbudowanej zmiennejRT
).Poniżej znajduje się test, którego użyłem do sprawdzenia, czy liczba wierszy każdego pliku to
mod 4 == 0
Wyjście testowe:
myfile
został wygenerowany przez:źródło
To nie jest poważna odpowiedź! Właśnie się bawiłemflex
i to najprawdopodobniej nie zadziała na pliku wejściowym o ~ 50 Gb (jeśli w ogóle, na większych danych wejściowych niż mój plik testowy):Działa to dla mnie na pliku ~ 1 Gb input.txt :
Biorąc pod uwagę
flex
plik wejściowy splitter.l :generowanie lex.yy.c i kompilowanie go do pliku
splitter
binarnego za pomocą:Stosowanie:
Czas działania dla wejścia 1 Gb. Txt :
źródło
getc(stream)
i zastosować prostą logikę. Czy wiesz również, że. (kropka) regex w (f) lex pasuje do dowolnego znaku oprócz nowej linii , prawda? Podczas gdy rekordy te są wieloliniowe.@
znak, a następnie pozwala domyślnej regule skopiować dane. Teraz masz regułę kopiującą część danych jako jeden duży token, a następnie domyślna reguła otrzymuje drugi wiersz po jednym znaku na raz.txr
.Oto rozwiązanie w Pythonie, które polega na przejściu pliku wejściowego i zapisaniu plików wyjściowych.
Cechą związaną z używaniem
wc -l
jest to, że zakładasz, że każdy z rekordów ma ten sam rozmiar. To może być prawda tutaj, ale poniższe rozwiązanie działa, nawet jeśli tak nie jest. Zasadniczo używawc -c
lub liczbę bajtów w pliku. W Pythonie odbywa się to za pomocą os.stat ()Oto jak działa program. Najpierw obliczamy idealne punkty podziału jako przesunięcia bajtów. Następnie odczytujesz wiersze pliku wejściowego zapisywane do odpowiedniego pliku wyjściowego. Gdy zobaczysz, że przekroczyłeś optymalny następny punkt podziału i jesteś na granicy rekordu, zamknij ostatni plik wyjściowy i otwórz następny.
Program jest optymalny pod tym względem, odczytuje raz bajty pliku wejściowego; Uzyskanie rozmiaru pliku nie wymaga odczytu danych pliku. Wymagane miejsce do przechowywania jest proporcjonalne do wielkości linii. Ale Python lub system prawdopodobnie mają rozsądne bufory plików, aby przyspieszyć operacje wejścia / wyjścia.
Dodałem parametry określające liczbę plików do podzielenia i wielkość rekordu na wypadek, gdybyś chciał to zmienić w przyszłości.
I oczywiście można to przetłumaczyć również na inne języki programowania.
Jeszcze jedno, nie jestem pewien, czy Windows z crlf odpowiednio obsługuje długość linii, tak jak to ma miejsce w systemach Unix-y. Jeśli len () jest wyłączony o jeden tutaj, mam nadzieję, że to oczywiste, jak dostosować program.źródło
printf %s\\n {A..Z}{A..Z}{A..Z}{A..Z}—{1..4}
Użytkownik FloHimself wydawał się ciekawy rozwiązania TXR . Oto jeden z wykorzystaniem wbudowanego TXR Lisp :
Uwagi:
Z tego samego powodu
pop
- ważne jest użycie każdej krotki z leniwej listy krotek, aby leniwa lista została wykorzystana. Nie możemy zachować odniesienia do początku tej listy, ponieważ wtedy pamięć będzie rosła podczas marszu przez plik.(seek-stream fo 0 :from-current)
jest przypadkiem braku operacjiseek-stream
, co czyni się przydatnym, zwracając bieżącą pozycję.Wydajność: nie wspominaj o tym. Użyteczne, ale nie przyniosą żadnych trofeów do domu.
Ponieważ sprawdzamy rozmiar co 1000 krotek, możemy po prostu utworzyć krotkę o wielkości 4000 linii.
źródło
Jeśli nie potrzebujesz, aby nowe pliki były ciągłymi częściami oryginalnego pliku, możesz to zrobić
sed
w następujący sposób:-n
Zatrzymuje go przed wydrukowaniem każdą linię, a każdy z tych-e
scenariuszy jest w zasadzie robi to samo.1~16
dopasowuje pierwszą linię, a następnie co 16 linię.,+3
oznacza dopasowanie kolejnych trzech wierszy po każdym z nich.w1.txt
mówi napisz wszystkie te wiersze do pliku1.txt
. Zajmuje to co 4 grupę 4 linii i zapisuje ją do pliku, zaczynając od pierwszej grupy 4 linii. Pozostałe trzy polecenia robią to samo, ale każda z nich jest przesuwana do przodu o 4 linie i zapisuje do innego pliku.Spowoduje to okropne uszkodzenie, jeśli plik nie będzie dokładnie zgodny z ustaloną specyfikacją, ale w przeciwnym razie powinien działać zgodnie z zamierzeniami. Nie wyprofilowałem go, więc nie wiem, jak skuteczny będzie, ale
sed
jest dość wydajny w edycji strumieniowej.źródło