Zastępowanie ciągów w bardzo dużym pliku

10

Mam bardzo długą serię adresów URL bez znaku oddzielającego, w tym samym formacie co poniżej:

http://example.comhttp://example.nethttp://example.orghttp://etc...

Chcę, aby każdy adres URL był w nowym wierszu. Próbowałem to zrobić, zastępując wszystkie wystąpienia „http: //” ciągiem „\ nhttp: //” za pomocą sed

sed 's_http://_\nhttp://_g' urls.txt

ale występuje błąd segmentacji (naruszenie pamięci). Mogę tylko przypuszczać, że sama wielkość pliku (ponad 100 GB) powoduje, że sed przekracza pewien limit.

Mógłbym podzielić plik na kilka mniejszych plików do przetworzenia, ale wszystkie wystąpienia „http: //” musiałyby pozostać nienaruszone.

Czy jest na to lepszy sposób?

C Sawyer
źródło
Myślę, że sed nie lubi 100 GB bez zakończeń linii, ponieważ próbuje odczytać jedną linię w swoim buforze.
jippie
dzielenie (niezależnie od tego, gdzie „dzieje się” cięcie), przetwarzanie, a następnie ponowne składanie powinno dać jednak poprawny wynik.
enzotib
3
Jeśli naprawdę masz plik tekstowy o pojemności 100 GB zawierający pojedynczą długą linię, lepiej jest napisać szybki program C do wykonania pracy.
fpmurphy

Odpowiedzi:

11

Dzięki temu awkmożesz uniknąć czytania dużej ilości tekstu na raz:

awk -vRS='http://' -vORS='\nhttp://' 1 urls.txt > urlsperline.txt

Sukces może zależeć od zastosowanej awkimplementacji. Na przykład gawkdziała dobrze, ale mawkulega awarii.

człowiek w pracy
źródło
6

Spowoduje to wykonanie zadania:

perl -pe 'BEGIN { $/ = "//" } s!(?=http://\z)!\n!' urls.txt

Ustawiając $ / , zmieniłem definicję linii, tak aby kończyła się na //zamiast nowej linii. To sprawia, że ​​Perl czyta jeden adres URL na raz. Jest mało prawdopodobne, że adres URL zawiera //oprócz schematu, ale jeśli tak się stanie, wyrażenie regularne uniemożliwi dodawanie fałszywych znaków nowego wiersza.

Jeśli chcesz uniknąć dodawania pustego wiersza przed pierwszym adresem URL:

perl -pe 'BEGIN { $/ = "//"; print scalar <> } s!(?=http://\z)!\n!' urls.txt

Możesz spróbować przeprowadzić testy porównawcze, aby sprawdzić, czy s!http://\z!\nhttp://!jest szybsze. Są równoważne. Zauważ, że /gflaga nie jest konieczna przy podstawieniu, ponieważ może być tylko jedno dopasowanie na „linię”.

cjm
źródło
Czy silnik wyrażenia regularnego perl jest w porządku z liniami o długości wielu gigabajtów?
Alexios
2
@Alexios, prawdopodobnie nie, ale nie musi tak być. Odkąd się zmieniłem $/, będzie dotyczył tylko jednego adresu URL na raz.
cjm
Ach, widzę, co tam zrobiłeś. Minęło trochę czasu od lat 90. i musiałem man perlvar, ale ma to sens.
Alexios
Linux pozwala, aby adresy URL zawierały wiele ukośników w ścieżkach, więc ten kod może się nie powieść, jeśli masz któryś z nich. Testowanie całego łańcucha, http i wszystkich, nie będzie miało tego problemu.
Joe
@Joe, testuję http:część wyrażenia regularnego. Sprawdza każdy //, ale nie doda nowego wiersza, chyba że znajdzie http://.
cjm
5
  1. Zmień wszystkie wystąpienia :za pomocą nowej linii, aby posiekać plik.
  2. Zastąpić
    • http na końcu linii z
    • nowa linia, po której następuje http:i dołącza do niej następną linię
  3. Powtórz raz, aby linie parzyste i nieparzyste zostały zaktualizowane

Te kroki wyglądają następująco:

tr ':' '\n' | sed -e '/http$/{N;s/http\n/\nhttp:/}' | sed -e '/http$/{N;s/http\n/\nhttp:/}'
  1. Sprawdź, czy są linie, które nie zaczynają się od http://, wydrukuj numery linii. Dzieje się tak tylko wtedy, gdy: jest gdzieś w adresie URL innym niż po http.

    grep -nv '^http://'

jippie
źródło