nieliniowe narzędzie do zamiany łańcucha?


13

Niedawno zadałem pytanie, jak usunąć znak nowej linii, jeśli występuje on po innym określonym znaku.

Uniksowe narzędzia do przetwarzania tekstu są bardzo wydajne, ale prawie wszystkie zajmują się liniami tekstu, co jest w porządku przez większość czasu, gdy dane wejściowe mieszczą się w dostępnej pamięci.

Ale co powinienem zrobić, jeśli chcę zastąpić sekwencję tekstową w wielkim pliku, który nie zawiera żadnych znaków nowej linii?

Przykładowo wymienić <foobar>ze \n<foobar>bez czytania linia po linii wejściowych? (ponieważ jest tylko jedna linia i ma długość 2,5G znaków).


1
Czy jesteś otwarty na używanie perllub python?
iruvar

Perl ma się dobrze. Właśnie znalazłem gsar( home.online.no/~tjaberg ), którego spróbuję.
MattBianco

Odpowiedzi:


12

Pierwszą rzeczą, która przychodzi mi do głowy w obliczu tego rodzaju problemu, jest zmiana separatora rekordów. W większości narzędzi jest to ustawione \ndomyślnie, ale można to zmienić. Na przykład:

  1. Perl

    perl -0x3E -pe 's/<foobar>/\n$&/' file
    

    Wyjaśnienie

    • -0: ustawia separator rekordów wejściowych na znak, biorąc pod uwagę jego wartość szesnastkową . W tym przypadku ustawiam na >wartość heksadecymalną 3E. Ogólny format to -0xHEX_VALUE. To tylko sztuczka, aby przełamać linię na porcje do zarządzania.
    • -pe: wydrukuj każdy wiersz wejściowy po zastosowaniu skryptu podanego przez -e.
    • s/<foobar>/\n$&/: prosta zamiana. W $&tym przypadku jest to, co zostało dopasowane <foobar>.
  2. awk

    awk '{gsub(/foobar>/,"\n<foobar>");printf "%s",$0};' RS="<" file
    

    Wyjaśnienie

    • RS="<": ustaw separator rekordów wejściowych na >.
    • gsub(/foobar>/,"\n<foobar>"): Zastąpić wszystkie przypadki foobar>z \n<foobar>. Zauważ, że ponieważ RSzostało ustawione na <, wszystkie <są usuwane z pliku wejściowego (tak to awkdziała), więc musimy dopasować foobar>(bez a <) i zastąpić \n<foobar>.
    • printf "%s",$0: wydrukuj bieżącą „linię” po zamianie. $0jest bieżącym rekordem, awkwięc pomieści wszystko, co było przed <.

Przetestowałem je na 2,3 GB, jednowierszowym pliku utworzonym za pomocą następujących poleceń:

for i in {1..900000}; do printf "blah blah <foobar>blah blah"; done > file
for i in {1..100}; do cat file >> file1; done
mv file1 file

Zarówno użyta, jak awki perlznikoma ilość pamięci.


Czy próbowałeś kiedyś Tie::File perldoc.perl.org/Tie/File.html . Myślę, że to najlepsze cechy w Perlprzypadku dużych plików.
cuonglm

@Gnouc Grałem trochę, tak. Ale i) OP już wcześniej wyraził niechęć do Perla, więc chciałem to uprościć. Ii) Staram się unikać korzystania z zewnętrznych modułów, chyba że jest to absolutnie konieczne, oraz iii) Korzystanie z modułu Tie :: File znacznie zmniejszyłoby składnię jasny.
terdon

Zgodzić się. Mała uwaga, która od tego czasu Tie::Filejest modułem podstawowym v5.7.3.
cuonglm

9

gsar (ogólne wyszukiwanie i zamiana) jest bardzo przydatnym narzędziem do tego właśnie celu.

Większość odpowiedzi na to pytanie wykorzystuje narzędzia oparte na rekordach i różne sztuczki, aby dostosować je do problemu, na przykład zmienić domyślny znak separatora rekordów na coś, co często pojawia się na wejściu, aby nie powodować, że każdy rekord jest zbyt duży, aby go obsłużyć.

W wielu przypadkach jest to bardzo dobre, a nawet czytelne. Lubię problemów, które mogą być łatwo / efektywnie rozwiązany wszędzie dostępnych narzędzi, takich jak awk, tr, seda powłoka Bourne.

Przeprowadzenie wyszukiwania binarnego i zamiana w dowolny ogromny plik z losową zawartością nie pasuje zbyt dobrze do tych standardowych narzędzi uniksowych.

Niektórzy z was mogą myśleć, że to oszustwo, ale nie rozumiem, w jaki sposób niewłaściwe może być użycie odpowiedniego narzędzia. W tym przypadku jest to program typu C, gsarktóry jest licencjonowany na licencji GPL v2 , więc trochę mnie zaskakuje, że nie ma pakietu dla tego bardzo użytecznego narzędzia ani w Gentoo , Redhat , ani Ubuntu .

gsarużywa binarnego wariantu algorytmu wyszukiwania ciągów Boyera-Moore'a .

Użycie jest proste:

gsar -F '-s<foobar>' '-r:x0A<foobar>'

gdzie -Foznacza tryb „filtrowania”, tj. odczyt stdinzapisu do stdout. Istnieją również metody działania na plikach. -sokreśla szukany ciąg i -rzamiennik. Notacji dwukropka można użyć do określenia dowolnych wartości bajtów.

Obsługiwany jest tryb bez rozróżniania wielkości liter ( -i), ale nie ma obsługi wyrażeń regularnych, ponieważ algorytm wykorzystuje długość ciągu wyszukiwania do optymalizacji wyszukiwania.

Narzędzie może być również używane do wyszukiwania, trochę podobnie grep. gsar -bwypisuje bajtowe przesunięcia dopasowanego ciągu wyszukiwania i gsar -ldrukuje nazwę pliku i liczbę dopasowań, jeśli takie istnieją, trochę jak łączenie grep -lz wc.

Narzędzie zostało napisane przez Tormoda Tjaberga (inicjał) i Hansa Petera Verne'a (ulepszenia).


Jeśli jest to licencja GPL, czy możesz rozważyć zapakowanie go na dystrybucję :)
Rqomey

1
Właściwie myślę raczej poważnie o stworzeniu ebuilda dla Gentoo. Może także rpm. Ale nigdy wcześniej nie budowałem pakietu .deb, więc mam nadzieję, że ktoś mnie w nim pobije (ponieważ zajmie mi to trochę czasu).
MattBianco

Wątpię, żeby to było pocieszenie, ale homebrew OS X ma wzór na gsar.
crazysim,

5

W wąskim przypadku, gdy ciągi docelowe i zastępcze mają tę samą długość, na ratunek może przyjść mapowanie pamięci . Jest to szczególnie przydatne, jeśli wymiana musi być wykonana na miejscu. Zasadniczo mapujesz plik do pamięci wirtualnej procesu, a przestrzeń adresowa dla adresowania 64-bitowego jest ogromna. Należy pamiętać, że plik niekoniecznie jest jednocześnie mapowany do pamięci fizycznej , więc można poradzić sobie z plikami, które są kilkakrotnie większe niż wielkość pamięci fizycznej dostępnej na komputerze.

Oto przykład Pythona, który zastępuje foobarsięXXXXXX

#! /usr/bin/python
import mmap
import contextlib   
with open('test.file', 'r+') as f:
 with contextlib.closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_WRITE)) as m:
   pos = 0
   pos = m.find('foobar', pos)
   while pos > 0:
    m[pos: pos+len('XXXXXX')] = 'XXXXXX'
    pos = m.find('foobar', pos)

4

Jest na to wiele narzędzi:

ddto jest to, czego chcesz użyć, jeśli chcesz zablokować plik - niezawodnie odczytaj tylko określoną liczbę bajtów tylko określoną liczbę razy. Przenośnie obsługuje blokowanie i odblokowywanie strumieni plików:

tr -dc '[:graph:]' </dev/urandom | dd bs=32 count=1 cbs=8 conv=unblock,sync 2>/dev/null

###OUTPUT###

UI(#Q5\e BKX2?A:Z RAxGm:qv t!;/v!)N

Używam również trpowyżej, ponieważ może obsłużyć konwersję dowolnego bajtu ASCII na dowolny inny (lub, w tym przypadku, usunięcie dowolnego bajtu ASCII, który nie jest znakiem spacji nieprzeznaczonym do spacji). Właśnie tego użyłem w odpowiedzi na twoje inne pytanie dziś rano, kiedy to zrobiłem:

tr '>\n' '\n>' | sed 's/^>*//' | tr '\n>' '>\n' 

Istnieje wiele podobnych . Ta lista powinna zawierać najniższy podzbiór wspólnego mianownika, z którym możesz się zapoznać.

Ale gdybym miał zamiar przetwarzać tekst na pliku binarnym o pojemności 2,5 GB, mógłbym zacząć od. Może dać ci jeden octal dumplub kilka innych formatów. Możesz określić wszystkie rodzaje opcji - ale zrobię tylko jeden bajt na linię w \Cformacie zmiany znaczenia:

Dane, z których będziesz uzyskiwać, odbędą regularne w dowolnych odstępach czasu, które określisz - jak pokazano poniżej. Ale najpierw - oto odpowiedź na twoje pytanie:

printf 'first\nnewline\ttab spacefoobar\0null' |
od -A n -t c -v -w1 |
sed 's/^ \{1,3\}//;s/\\$/&&/;/ /bd
     /\\[0nt]/!{H;$!d};{:d
    x;s/\n//g}'

Że trochę powyżej wyznacza na \newlines, \0null, \tABS i <spaces>przy zachowaniu \Cuciekł ciąg do separatora. Zwróć uwagę na używane funkcje Hi x- za każdym razem, gdy sednapotka ogranicznik, wymienia zawartość swoich buforów pamięci. W ten sposób sedzachowuje tylko tyle informacji, ile musi, aby niezawodnie rozgraniczić plik i nie ulega przepełnieniu bufora - to znaczy tak długo, jak faktycznie napotyka swoje ograniczniki. Tak długo, jak to zrobi, sedbędzie przetwarzał dane wejściowe i odbędzie je dostarczał, dopóki nie napotka EOF.

Jak widać, jego dane wyjściowe wyglądają tak:

first
\nnewline
\ttab
 spacefoobar
\0null

Więc jeśli chcę foobar:

printf ... | od ... | sed ... | 
sed 's/foobar/\
&\
/g'

###OUTPUT###

first
\nnewline
\ttab
 space
foobar

\0null

Teraz, jeśli chcesz użyć tych Cznaków, jest to całkiem proste - ponieważ sedjuż podwójny \\ukośnik printfodwrócił wszystkie swoje pojedyncze ukośniki odwrotne, więc wykonanie z nie xargsbędzie miało problemów z produkcją danych wyjściowych zgodnie ze specyfikacją. Ale xargs zjada cytaty z powłoki, więc musisz jeszcze raz je zacytować:

printf 'nl\ntab\tspace foobarfoobar\0null' |
PIPELINE |
sed 's/./\\&/g' | 
xargs printf %b | 
cat -A

###OUTPUT###

nl$
tab^Ispace $
foobar$
$
foobar$
^@null%

Można to równie łatwo zapisać w zmiennej powłoki i wyprowadzić później w identyczny sposób. Ostatni sedwstawia \odwrotny ukośnik przed każdym znakiem na wejściu i to wszystko.

A oto, jak to wszystko wygląda, zanim sedsię to obejmie:

printf 'nl\ntab\tspace foobarfoobar\0null' |
od -A n -t c -v -w1

   n
   l
  \n
   t
   a
   b
  \t
   s
   p
   a
   c
   e

   f
   o
   o
   b
   a
   r
   f
   o
   o
   b
   a
   r
  \0
   n
   u
   l
   l

2

Awk działa na kolejnych zapisach. Może używać dowolnego znaku jako separatora rekordów (z wyjątkiem bajtu zerowego w wielu implementacjach). Niektóre implementacje obsługują dowolne wyrażenia regularne (niepasujące do pustego ciągu) jako separator rekordów, ale może to być nieporęczne, ponieważ separator rekordów jest obcinany od końca każdego rekordu przed jego umieszczeniem $0(GNU awk ustawia zmienną RTna separator rekordów który został usunięty z końca bieżącego rekordu). Zauważ, że printkończy swoje wyjście separatorem rekordów wyjściowych, ORSktóry jest domyślnie nowym wierszem i jest ustawiony niezależnie od separatora rekordów wejściowych RS.

awk -v RS=, 'NR==1 {printf "input up to the first comma: %s\n", $0}'

Można skutecznie wybrać inny charakter jako separator rekordu dla innych narzędzi ( sort, sed...) poprzez zamianę nowe linie z tego znaku z tr.

tr '\n,' ',\n' |
sed 's/foo/bar/' |
sort |
tr '\n,' ',\n'

Wiele narzędzi tekstowych GNU obsługuje separator jako bajt zerowy zamiast nowego wiersza.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.