Mam ogromny (70 GB), jeden wiersz , plik tekstowy i chcę w nim zastąpić ciąg (token). Chcę zastąpić token <unk>
innym tokenem zastępczym ( problem z rękawiczkami ).
Próbowałem sed
:
sed 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
ale plik wyjściowy corpus.txt.new
ma zero bajtów!
Próbowałem także użyć perla:
perl -pe 's/<unk>/<raw_unk>/g' < corpus.txt > corpus.txt.new
ale wystąpił błąd braku pamięci.
W przypadku mniejszych plików działają oba powyższe polecenia.
Jak mogę zamienić ciąg znaków na taki plik? To powiązane pytanie, ale żadna z odpowiedzi nie zadziałała dla mnie.
Edycja : Co powiesz na podzielenie pliku na części po 10 GB (lub cokolwiek innego) i zastosowanie sed
do każdego z nich, a następnie scalenie ich cat
? Czy to ma sens? Czy istnieje bardziej eleganckie rozwiązanie?
text-processing
sed
large-files
Christos Baziotis
źródło
źródło
split
z-b
opcją definiowania wielkości plików porcji w bajtach. Przetwarzaj każdy po kolei za pomocąsed
i ponownie złóż. Istnieje ryzyko, że<unk>
można je podzielić na dwa pliki i nie można ich znaleźć ...Odpowiedzi:
Zwykłe narzędzia do przetwarzania tekstu nie są zaprojektowane do obsługi linii, które nie mieszczą się w pamięci RAM. Mają tendencję do pracy, czytając jeden rekord (jedną linię), manipulując nim i wyprowadzając wynik, a następnie przechodząc do następnego rekordu (linii).
Jeśli w pliku często pojawia się znak ASCII i nie pojawia się on w
<unk>
lub<raw_unk>
, możesz użyć go jako separatora rekordów. Ponieważ większość narzędzi nie pozwala na niestandardowe separatory rekordów, zamień między tym znakiem a znakami nowej linii.tr
przetwarza bajty, a nie wiersze, więc nie ma znaczenia wielkość rekordu. Załóżmy, że to;
działa:Możesz także zakotwiczyć pierwszy znak szukanego tekstu, zakładając, że nie jest on powtarzany w wyszukiwanym tekście i pojawia się wystarczająco często. Jeśli plik może zaczynać się
unk>
, zmień polecenie sed,sed '2,$ s/…
aby uniknąć fałszywego dopasowania.Możesz też użyć ostatniego znaku.
Zauważ, że ta technika zakłada, że sed działa bezproblemowo na pliku, który nie kończy się na nowej linii, tzn. Przetwarza ostatnią część linii bez obcinania jej i bez dołączania ostatniej linii. Działa z GNU sed. Jeśli możesz wybrać ostatni znak pliku jako separator rekordów, unikniesz problemów z przenośnością.
źródło
awk -v RS=, -v ORS=, '{gsub(/<unk>/, "<raw_unk>"); print}'
Nie?-0
i ósemkową wartością char, lub w skrypcie można ustawić specjalną zmienną$/
awk
unikaj dwukrotnego przekazywania strumieniatr
. Czy byłoby jeszcze wolniej?tr
jest bardzo szybki, a rura może być nawet równoległa.W przypadku tak dużego pliku jedną z możliwości jest Flex. Niech
unk.l
będzie:Następnie skompiluj i uruchom:
źródło
make
ma do tego domyślne reguły, zamiast flex / cc możesz dodać%option main
jako pierwszą linię unk.l, a potem tylkomake unk
. Mniej lub bardziej odruchowo używam%option main 8bit fast
i mamexport CFLAGS='-march=native -pipe -Os'
w sobie.bashrc
.%option main
+make
+ opcjonalnieCFLAGS
są bardzo fajną sztuczką !! Czy-march=native
zachowanie jest domyślne?Nie masz więc wystarczającej ilości pamięci fizycznej (RAM), aby pomieścić cały plik na raz, ale w systemie 64-bitowym masz wystarczająco dużo wirtualnej przestrzeni adresowej, aby zmapować cały plik. Wirtualne mapowania mogą być przydatne jako prosty hack w takich przypadkach.
Niezbędne operacje są zawarte w Pythonie. Istnieje kilka irytujących subtelności, ale unika się pisania kodu C. W szczególności należy zachować ostrożność, aby uniknąć kopiowania pliku do pamięci, co całkowicie zniweczyłoby ten problem. Z drugiej strony otrzymujesz bezpłatne raportowanie błędów (python „wyjątki”) :).
źródło
search
może zawierać znak NUL. I zauważam, że inna wersja C tutaj nie obsługuje znaków NULreplace
.). Zapraszamy do uzyskania wersji C w celach porównawczych. Pamiętaj jednak, że moja wersja zawiera podstawowe raportowanie błędów dla wykonywanych operacji. Wersja C będzie przynajmniej bardziej irytujące czytać IMO, gdy raportowanie błędów jest włączone.Myślę, że wersja C może działać znacznie lepiej:
EDYCJA: Zmodyfikowana zgodnie z sugestiami z komentarzy. Naprawiono również błąd we wzorze
<<unk>
.źródło
memcpy
Szybkość (tj. wąskie gardło pamięci) to około 12 GB / sekundę na najnowszym procesorze x86 (np. Skylake). Nawet w przypadku narzutu wywołania systemowego stdio +, w przypadku pliku o pojemności 30 MB w buforze pamięci podręcznej dysku, oczekiwałbym może 1 GB / sekundę dla wydajnej implementacji. Czy skompilowałeś kompilację z wyłączoną optymalizacją, czy jest to naprawdę tak wolno we / wy?getchar_unlocked
/putchar_unlocked
może pomóc, ale zdecydowanie lepiej jest czytać / pisać w kawałkach o wielkości może 128kiB (połowa wielkości pamięci podręcznej L2 na większości procesorów x86, więc najczęściej trafiasz w L2 podczas zapętlania po przeczytaniu)fix
Do programu"<<unk>"
nadal nie działa, jeślipattern
zaczyna się od powtarzających się sekwencji znaków (czyli nie będzie działać, jeśli starali się zastąpić Mrówkojad z zebry i trzeba było wejście aaardvak, albo starali się zastąpić ababc i miał wkład abababc). Ogólnie rzecz biorąc, nie możesz przejść do przodu o liczbę przeczytanych znaków, chyba że wiesz, że nie ma możliwości dopasowania się do przeczytanych znaków.W
replace
pakiecie mariadb-server / mysql-server znajduje się narzędzie. Zastępuje proste łańcuchy (nie wyrażenia regularne) i w przeciwieństwie do grep / sed / awkreplace
nie dba o\n
i\0
. Zużycie pamięci jest stałe dla każdego pliku wejściowego (około 400 KB na moim komputerze).Oczywiście nie musisz uruchamiać serwera mysql, aby go używać
replace
, jest on spakowany tylko w ten sposób w Fedorze. Inne dystrybucje / systemy operacyjne mogą mieć to oddzielnie.źródło
GNU
grep
może pokazać przesunięcie dopasowania w plikach „binarnych”, bez konieczności wczytywania całych linii do pamięci. Następnie możesz użyćdd
do odczytu do tego przesunięcia, pominąć dopasowanie, a następnie kontynuować kopiowanie z pliku.Dla szybkości podzieliłem
dd
duży odczyt wielkości bloku 1048576 i mniejszy odczyt 1 bajtu naraz, ale ta operacja nadal będzie trochę powolna na tak dużym pliku. Danegrep
wyjściowe są na przykład13977:<unk>
podzielone na dwukropek przez odczyt na zmienneoffset
ipattern
. Musimy śledzić,pos
ile bajtów zostało już skopiowanych z pliku.źródło
Oto kolejna pojedyncza linia poleceń UNIX, która może działać lepiej niż inne opcje, ponieważ można „polować” na „rozmiar bloku”, który działa dobrze. Aby było to solidne, musisz wiedzieć, że masz co najmniej jedną spację na każde X znaków, gdzie X jest twoim dowolnym „rozmiarem bloku”. W poniższym przykładzie wybrałem „rozmiar bloku” wynoszący 1024 znaki.
Tutaj fold spakuje do 1024 bajtów, ale -s upewnia się, że łamie się na spacji, jeśli jest co najmniej jeden od ostatniej przerwy.
Polecenie sed należy do ciebie i robi to, czego oczekujesz.
Następnie polecenie tr „rozłoży” plik konwertujący nowe wiersze, które zostały wstawione z powrotem do niczego.
Powinieneś rozważyć wypróbowanie większych bloków, aby sprawdzić, czy działa ono szybciej. Zamiast 1024 możesz wypróbować 10240 oraz 102400 i 1048576 dla opcji -w fold.
Oto przykład w podziale według każdego kroku, który konwertuje wszystkie litery N na małe litery:
Będziesz musiał dodać nowy wiersz na samym końcu pliku, jeśli go ma, ponieważ polecenie tr go usunie.
źródło
Za pomocą
perl
Zarządzanie własnymi buforami
Możesz użyć
IO::Handle
'ssetvbuf
do zarządzania domyślnymi buforami lub możesz zarządzać swoimi własnymi buforami za pomocąsysread
isyswrite
. Sprawdźperldoc -f sysread
iperldoc -f syswrite
po więcej informacji, zasadniczo pomijają buforowane io.Tutaj rzucamy naszym własnym buforem IO, ale robimy to ręcznie i arbitralnie na 1024 bajtach. Otwieramy również plik RW, więc robimy to wszystko na tym samym FH na raz.
Jeśli zamierzasz wybrać tę trasę
<unk>
i<raw_unk>
są tej samej wielkości bajt.CHUNKSIZE
granicy, jeśli zastępujesz więcej niż 1 bajt.źródło
<unk>
spadnie na granicę między kawałkami?Możesz spróbować bbe ( edytor bloków binarnych ), „
sed
dla plików binarnych”.Odniosłem duży sukces, używając go w pliku tekstowym o pojemności 7 GB bez
EOL
znaków, zastępując wiele wystąpień ciągu jednym o różnej długości. Bez próby optymalizacji dało to średnią przepustowość przetwarzania> 50 MB / s.źródło
Dzięki
perl
możesz pracować z rekordami o stałej długości, takimi jak:I mam nadzieję, że nie będzie
<unk>
dwóch takich 100 MB rekordów.źródło
while read -N 1000 chunk;
(1000
wybrano jako przykład). Rozwiązaniem problemu z<unk>
podziałem na fragmenty są dwa przejścia przez plik: pierwszy z fragmentami 100 MB, a drugi z fragmentami „100 MB + 5 bajtów”. Ale nie jest to optymalne rozwiązanie w przypadku pliku 70 GB.<unk>
.<unk>
zdarzenia są dalekie, jeśli nie, użyj$/ = ">"
is/<unk>\z/<raw_unk>/g
) poprawności.Oto mały program Go, który wykonuje zadanie (
unk.go
):Po prostu zbuduj go
go build unk.go
i uruchom jako./unk <input >output
.EDYTOWAĆ:
Przepraszam, nie przeczytałem, że wszystko jest w jednej linii, więc próbowałem teraz odczytać plik znak po znaku.
EDYCJA II:
Zastosowano taką samą poprawkę jak dla programu C.
źródło
scanner.Split(bufio.ScanRunes)
robi magię.go doc bufio.MaxScanTokenSize
domyślny rozmiar bufora.C
program, nie działa to w przypadku zamiany aardwarku na zebrę z wejściem aaardwarku.Może to być nadmiar w przypadku pliku o pojemności 70 GB oraz proste wyszukiwanie i zamiana, ale platforma Hadoop MapReduce rozwiązałaby teraz twój problem bez żadnych kosztów (wybierz opcję „Single Node” podczas konfigurowania, aby uruchomić go lokalnie) - i może być skalowane do nieskończonej pojemności w przyszłości bez potrzeby modyfikowania kodu.
Oficjalny samouczek na https://hadoop.apache.org/docs/stable/hadoop-mapreduce-client/hadoop-mapreduce-client-core/MapReduceTutorial.html używa (bardzo prostej) Java, ale można znaleźć biblioteki klienta dla Perla lub w jakim języku chcesz.
Więc jeśli później okaże się, że wykonujesz bardziej złożone operacje na plikach tekstowych o wielkości 7000 GB - i musisz to robić 100 razy dziennie - możesz rozłożyć obciążenie na wiele węzłów, które udostępniasz lub które są automatycznie udostępniane dla Ciebie przez chmurę - oparty klaster Hadoop.
źródło
Wszystkie poprzednie sugestie wymagają odczytania całego pliku i zapisania całego pliku. Zajmuje to nie tylko dużo czasu, ale także wymaga 70 GB wolnego miejsca.
1) Jeśli dobrze rozumiem konkretny przypadek, czy dopuszczalne byłoby zastąpienie innym ciągiem o takiej samej długości?
2a) Czy występuje wiele wystąpień? 2b) Jeśli tak, to ile wiesz?
Jestem pewien, że rozwiązałeś już ten ponad rok problem i chciałbym wiedzieć, jakiego rozwiązania użyłeś.
Zaproponowałbym rozwiązanie (najprawdopodobniej w C), które czytałoby BLOKI pliku przeszukując każdy ciąg, biorąc pod uwagę możliwe krzyżowanie bloków. Po znalezieniu zamień ciąg na przemienny o tej samej długości i zapisz tylko ten BLOK. Kontynuacja dla znanej liczby wystąpień lub do końca pliku. Wymagałoby to zaledwie kilku zapisów i co najwyżej dwukrotnie więcej (jeśli każde wystąpienie zostało podzielone na 2 bloki). Nie wymagałoby to dodatkowej przestrzeni!
źródło
Jeśli mamy minimalną kwotę
<unk>
(zgodnie z prawem Zipf),źródło
sed
. Niezależnie od tego czyta wiersz do pamięci. Nie będzie w stanie zmieścić się w tej linii.sed
nie będzie buforować wejścia / wyjścia podczas używania tej flagi. Nie widzę, że będzie czytać wiersze częściowe.