Mam dość duży plik (35 GB) i chciałbym filtrować ten plik in situ (tzn. Nie mam wystarczającej ilości miejsca na inny plik), a konkretnie chcę grepować i ignorować niektóre wzorce - czy istnieje sposób na zrobić to bez użycia innego pliku?
Powiedzmy, że chcę odfiltrować wszystkie wiersze zawierające foo:
na przykład ...
Odpowiedzi:
Na poziomie wywołania systemowego powinno to być możliwe. Program może otworzyć plik docelowy do pisania bez obcinania go i rozpocząć zapisywanie tego, co czyta ze standardowego wejścia. Podczas odczytu EOF plik wyjściowy można obciąć.
Ponieważ filtrujesz linie z wejścia, pozycja zapisu pliku wyjściowego powinna zawsze być mniejsza niż pozycja odczytu. Oznacza to, że nie powinieneś uszkadzać danych wejściowych nowym wyjściem.
Problemem jest jednak znalezienie programu, który to robi.
dd(1)
ma opcjęconv=notrunc
, która nie obcina pliku wyjściowego przy otwartym, ale również nie obcina na końcu, pozostawiając oryginalną zawartość pliku po zawartości grep (przy pomocy polecenia podobnegogrep pattern bigfile | dd of=bigfile conv=notrunc
)Ponieważ jest to bardzo proste z perspektywy wywołań systemowych, napisałem mały program i przetestowałem go na małym (1MiB) systemie plików z pełną pętlą zwrotną. Zrobił to, co chciałeś, ale naprawdę chcesz najpierw przetestować to z innymi plikami. Zastąpienie pliku zawsze będzie ryzykowne.
overwrite.c
Użyłbyś go jako:
Przeważnie publikuję to, aby inni mogli komentować, zanim spróbujesz. Być może ktoś inny wie o programie, który robi coś podobnego, co jest bardziej testowane.
źródło
grep
nie wyśle więcej danych niż odczytuje, pozycja zapisu powinna zawsze znajdować się za pozycją odczytu. Nawet jeśli piszesz w tym samym tempie co czytanie, nadal będzie dobrze. Spróbuj rot13 z tym zamiast grep, a potem jeszcze raz. md5sum przed i po, a zobaczysz to samo.dd
, ale jest to uciążliwe.Możesz użyć
sed
do edycji plików w miejscu (ale to tworzy pośredni plik tymczasowy):Aby usunąć wszystkie wiersze zawierające
foo
:Aby zachować wszystkie wiersze zawierające
foo
:źródło
$HOME
będziesz mieć możliwość zapisu, ale/tmp
będzie tylko do odczytu (domyślnie). Na przykład, jeśli masz Ubuntu i uruchomiłeś konsolę odzyskiwania, zwykle tak jest. Również operator dokumentu tutaj też<<<
nie będzie tam działał, ponieważ wymaga/tmp
on r / w, ponieważ zapisze tam również plik tymczasowy. (por. to pytanie wraz zstrace
wyjściem „d”)Zakładam, że twoje polecenie filter jest tym, co nazywam filtrem zmniejszającym przedrostek , który ma właściwość polegającą na tym, że bajt N w danych wyjściowych nigdy nie jest zapisywany przed odczytaniem co najmniej N bajtów danych wejściowych.
grep
ma tę właściwość (o ile tylko filtruje i nie wykonuje innych czynności, takich jak dodawanie numerów wierszy dla dopasowań). Za pomocą takiego filtra możesz nadpisywać wprowadzane dane. Oczywiście musisz być pewien, że nie popełnisz błędu, ponieważ nadpisana część na początku pliku zostanie utracona na zawsze.Większość narzędzi uniksowych daje jedynie opcję dołączenia do pliku lub obcięcia go, bez możliwości zastąpienia go. Jedynym wyjątkiem w standardowym zestawie narzędzi jest to
dd
, że można powiedzieć, aby nie obcinał swojego pliku wyjściowego. Zatem plan polega na przefiltrowaniu poleceniadd conv=notrunc
. Nie zmienia to rozmiaru pliku, więc pobieramy również długość nowej zawartości i skracamy plik do tej długości (ponownie za pomocądd
). Pamiętaj, że to zadanie z natury nie jest niezawodne - jeśli wystąpi błąd, jesteś sam.Możesz napisać szorstki odpowiednik Perla. Oto szybkie wdrożenie, które nie próbuje być skuteczne. Oczywiście możesz również przeprowadzić wstępne filtrowanie bezpośrednio w tym języku.
źródło
Z każdą powłoką podobną do Bourne'a:
Z jakiegoś powodu wydaje się, że ludzie zapominają o tym 40-latku¹ i standardowym operatorze przekierowania odczytu i zapisu.
Otwieramy
bigfile
w trybie odczytu i zapisu + (co najważniejsze tutaj) bez obcięcia nastdout
czasbigfile
jest otwarty (osobno) nacat
„sstdin
. Pogrep
zakończeniu i jeśli usunął niektóre linie,stdout
teraz wskazuje gdzieś wewnątrzbigfile
, musimy pozbyć się tego, co jest poza tym punktem. Stądperl
polecenie, które obcina plik (truncate STDOUT
) w bieżącej pozycji (zwróconej przeztell STDOUT
).(
cat
dotyczy GNU,grep
który w przeciwnym razie narzeka, jeśli stdin i stdout wskazują ten sam plik).¹ Cóż, chociaż
<>
znajdował się w powłoce Bourne'a od początku lat siedemdziesiątych, początkowo był nieudokumentowany i nie został właściwie zaimplementowany . Nie było to w oryginalnej implementacjiash
z 1989 roku i chociaż jest tosh
operator przekierowywania POSIX (od wczesnych lat 90., ponieważsh
oparty jest na POSIX,ksh88
który zawsze go miał), nie został dodany do FreeBSDsh
na przykład do 2000 roku, więc przenośnie 15 lat stary jest prawdopodobnie dokładniejszy. Zauważ też, że domyślny deskryptor pliku, gdy nie jest określony, znajduje się<>
we wszystkich powłokach, z wyjątkiem tego, że w 2010 rksh93
. Zmienił się z 0 na 1 w ksh93t + (łamanie kompatybilności wstecznej i zgodności z POSIX)źródło
perl -e 'truncate STDOUT, tell STDOUT'
? Działa dla mnie bez uwzględnienia tego. Jest jakiś sposób na osiągnięcie tego samego bez Perla?redirection "<>" fixed and documented (used in /etc/inittab f.i.).
to jedna wskazówka.Chociaż jest to stare pytanie, wydaje mi się, że jest to pytanie odwieczne i dostępne jest bardziej ogólne, jaśniejsze rozwiązanie, niż dotychczas sugerowano. Kredyt tam, gdzie należny jest kredyt: nie jestem pewien, czy wymyśliłbym to bez wzmianki o Stéphane Chazelas o
<>
operatorze aktualizacji.Otwarcie pliku do aktualizacji w powłoce Bourne'a ma ograniczone zastosowanie. Powłoka nie umożliwia wyszukiwania pliku ani ustawiania jego nowej długości (jeśli jest krótsza niż stara). Ale łatwo to naprawić, więc jestem zaskoczony, że nie jest to standardowe narzędzie
/usr/bin
.To działa:
Podobnie jak to (czapka dla Stéphane'a):
(Używam GNU grep. Być może coś się zmieniło, odkąd napisał swoją odpowiedź).
Tyle że nie masz / usr / bin / ftruncate . Aby zobaczyć kilkadziesiąt linii C, możesz zobaczyć poniżej. To narzędzie ftruncate obcina dowolny deskryptor pliku do dowolnej długości, domyślnie ustawiając standardowe wyjście i bieżącą pozycję.
Powyższe polecenie (pierwszy przykład)
T
celu aktualizacji. Podobnie jak w przypadku open (2), otwarcie pliku w ten sposób ustawia bieżące przesunięcie na 0.T
normalnie, a powłoka przekierowuje swoje wyjście naT
deskryptor 4.Następnie podpowłoka kończy działanie, zamykając deskryptor 4. Oto ftruncate :
Uwaga: ftruncate (2) nie może być importowany, jeśli jest używany w ten sposób. Aby uzyskać absolutną ogólność, przeczytaj ostatni zapisany bajt, ponownie otwórz plik O_WRONLY, wyszukaj, zapisz bajt i zamknij.
Biorąc pod uwagę, że pytanie ma 5 lat, powiem, że to rozwiązanie jest nieoczywiste. Korzysta z exec, aby otworzyć nowy deskryptor, a
<>
operator, oba są tajemne. Nie mogę wymyślić standardowego narzędzia, które manipuluje i-węzłem za pomocą deskryptora pliku. (Składnia może byćftruncate >&4
, ale nie jestem pewien, czy poprawa.) Jest znacznie krótsza niż kompetentna, eksploracyjna odpowiedź camh. Jest tylko trochę jaśniejszy niż Stéphane, IMO, chyba że bardziej lubisz Perla niż ja. Mam nadzieję, że ktoś uzna to za przydatne.Innym sposobem na zrobienie tego samego byłaby wykonywalna wersja lseek (2), która zgłasza bieżące przesunięcie; wyjście może być wykorzystane do / usr / bin / truncate , które zapewniają niektóre Linuxi.
źródło
ed
jest prawdopodobnie właściwym wyborem do edycji pliku w miejscu:źródło
ed
wersje zachowują się inaczej ..... to pochodzi zman ed
(GNU Ed 1.4) ...If invoked with a file argument, then a copy of file is read into the editor's buffer. Changes are made to this copy and not directly to file itself.
ed
nie jest to dobre rozwiązanie do edycji plików 35 GB, ponieważ plik jest wczytywany do bufora.!
), więc może mieć kilka ciekawych sztuczek w zanadrzu.ed
skraca plik i przepisuje go. Więc to nie zmieni danych na dysku w miejscu, jak chce OP. Ponadto nie może działać, jeśli plik jest zbyt duży, aby załadować go do pamięci.Możesz użyć deskryptora pliku bash do odczytu / zapisu, aby otworzyć swój plik (aby go zastąpić na miejscu), a następnie
sed
itruncate
... ale oczywiście nie dopuść do tego, aby zmiany były większe niż ilość odczytanych danych .Oto skrypt (używa: zmienna bash $ BASHPID)
Oto wynik testu
źródło
Chciałbym zmapować plik w pamięci, zrobić wszystko w miejscu za pomocą wskaźników char * do nagiej pamięci, a następnie odwzorować plik i obciąć go.
źródło
Nie do końca na miejscu, ale - może to być przydatne w podobnych okolicznościach.
Jeśli miejsce na dysku stanowi problem, najpierw skompresuj plik (ponieważ jest to tekst, co da ogromną redukcję), a następnie użyj sed (lub grep, lub cokolwiek innego) w zwykły sposób w środku potoku dekompresji / kompresji.
źródło
sed -e '/foo/d' MyFile | gzip -c >MyEditedFile.gz && gzip -dc MyEditedFile.gz >MyFile
Z korzyścią dla każdego, kto przejrzy to pytanie, poprawną odpowiedzią jest przestanie szukać niejasnych funkcji powłoki, które grożą uszkodzeniem pliku z powodu nieznacznego wzrostu wydajności, i zamiast tego należy użyć pewnej odmiany tego wzorca:
Tylko w niezwykle rzadkiej sytuacji , gdy z jakiegoś powodu nie jest to możliwe, powinieneś poważnie rozważyć inne odpowiedzi na tej stronie (chociaż z pewnością są interesujące do przeczytania). Przyznaję, że zagadka OP polegająca na braku miejsca na dysku do utworzenia drugiego pliku jest właśnie taką sytuacją. Chociaż nawet wtedy są dostępne inne opcje, np. Dostarczone przez @Ed Randall i @Basile Starynkevitch.
źródło
echo -e "$(grep pattern bigfile)" >bigfile
źródło
grepped
dane przekraczają długość dozwoloną przez wiersz poleceń. następnie psuje dane