Mam dziwny problem z dużymi plikami i bash
. Oto kontekst:
- Mam duży plik: 75G i ponad 400 000 000 linii (to plik dziennika, mój zły, pozwoliłem mu urosnąć).
- Pierwsze 10 znaków każdej linii to znaczniki czasu w formacie RRRR-MM-DD.
- Chcę podzielić ten plik: jeden plik dziennie.
Próbowałem z następującym skryptem, który nie działał. Moje pytanie dotyczy tego, że ten skrypt nie działa, a nie alternatywnych rozwiązań .
while read line; do
new_file=${line:0:10}_file.log
echo "$line" >> $new_file
done < file.log
Po debugowaniu znalazłem problem w new_file
zmiennej. Ten skrypt:
while read line; do
new_file=${line:0:10}_file.log
echo $new_file
done < file.log | uniq -c
podaje wynik poniżej (umieszczam te zasady, x
aby zachować poufność danych, inne znaki są prawdziwe). Zwróć uwagę na dh
i krótsze ciągi:
...
27402 2011-xx-x4
27262 2011-xx-x5
22514 2011-xx-x6
17908 2011-xx-x7
...
3227382 2011-xx-x9
4474604 2011-xx-x0
1557680 2011-xx-x1
1 2011-xx-x2
3 2011-xx-x1
...
12 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
1 208--
1 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
...
To nie jest problem w formacie mojego pliku . Skrypt cut -c 1-10 file.log | uniq -c
podaje tylko ważne znaczniki czasu. Co ciekawe, część powyższej produkcji otrzymuje postać cut ... | uniq -c
:
3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1
Widzimy, że po zliczeniu uniq 4474604
mój początkowy skrypt nie powiódł się.
Czy osiągnąłem limit w bashu, którego nie znam, czy znalazłem błąd w bashu (wydaje się to mało prawdopodobne), czy też zrobiłem coś złego?
Aktualizacja :
Problem występuje po odczytaniu 2G pliku. Szwy read
i przekierowania nie lubią większych plików niż 2G. Ale wciąż szukam dokładniejszych wyjaśnień.
Aktualizacja 2 :
To definitywnie wygląda jak błąd. Można go powielać za pomocą:
yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c
ale działa to dobrze jako obejście (wydaje się, że znalazłem przydatne zastosowanie cat
):
cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c
Błąd został zgłoszony do GNU i Debiana. Dotknięte wersje to bash
4.1.5 w Debian Squeeze 6.0.2 i 6.0.4.
echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu
Aktualizacja 3:
Dzięki Andreasowi Schwabowi, który szybko zareagował na mój raport o błędzie, jest to łatka, która jest rozwiązaniem tego złego zachowania. Dotknięty plik jest, lib/sh/zread.c
jak wcześniej zauważył Gilles:
diff --git a/lib/sh/zread.c b/lib/sh/zread.c index 0fd1199..3731a41 100644
--- a/lib/sh/zread.c
+++ b/lib/sh/zread.c @@ -161,7 +161,7 @@ zsyncfd (fd)
int fd; { off_t off;
- int r;
+ off_t r;
off = lused - lind; r = 0;
r
Zmienna jest używana do przechowywania wartości zwracanej lseek
. Gdy lseek
zwraca przesunięcie od początku pliku, gdy przekracza 2 GB, int
wartość jest ujemna, co powoduje, że test if (r >= 0)
kończy się niepowodzeniem w miejscu, w którym powinien się powieść.
read
bash w kierunku granicy wyrażenia.Odpowiedzi:
Znalazłeś błąd w bashu. To znany błąd ze znaną poprawką.
Programy reprezentują przesunięcie w pliku jako zmienną w pewnym typie liczb całkowitych o skończonym rozmiarze. W dawnych czasach wszyscy używali
int
prawie wszystkiego, aint
typ był ograniczony do 32 bitów, łącznie z bitem znaku, dzięki czemu mógł przechowywać wartości od -2147483648 do 2147483647. Obecnie istnieją różne nazwy typów dla różnych rzeczy , w tymoff_t
dla przesunięcie w pliku.Domyślnie
off_t
jest to wersja 32-bitowa na platformie 32-bitowej (pozwalająca na maksymalnie 2 GB) i typ 64-bitowa na platformie 64-bitowej (pozwalająca na maksymalnie 8EB). Jednak często kompiluje się programy z opcją LARGEFILE, która przełącza ten typoff_t
na szerokość 64 bitów i sprawia, że program wywołuje odpowiednie implementacje funkcji takich jaklseek
.Wygląda na to, że uruchamiasz bash na platformie 32-bitowej, a twój plik bash nie jest skompilowany z obsługą dużych plików. Teraz, gdy czytasz wiersz ze zwykłego pliku, bash używa wewnętrznego bufora do odczytywania znaków partiami w celu zwiększenia wydajności (więcej szczegółów w źródle
builtins/read.def
). Po zakończeniu linii, bash wywołujelseek
przewinięcie przesunięcia pliku z powrotem do pozycji końca linii, na wypadek, gdyby jakiś inny program dbał o pozycję w tym pliku. Wywołanie dolseek
dzieje się wzsyncfc
funkcji wlib/sh/zread.c
.Nie przeczytałem źródła zbyt szczegółowo, ale przypuszczam, że coś nie dzieje się płynnie w punkcie przejścia, gdy przesunięcie absolutne jest ujemne. Więc bash kończy się czytaniem z niewłaściwymi przesunięciami, kiedy uzupełnia swój bufor, po przekroczeniu znaku 2GB.
Jeśli mój wniosek jest błędny, a twoja gra bash działa na platformie 64-bitowej lub jest skompilowana z obsługą dużych plików, to zdecydowanie błąd. Zgłoś to do swojej dystrybucji lub na wyższym szczeblu .
I tak powłoka nie jest odpowiednim narzędziem do przetwarzania tak dużych plików. Będzie powoli. Jeśli to możliwe, użyj sed, w przeciwnym razie awk.
źródło
Nie wiem o złym, ale z pewnością jest zawiłe. Jeśli twoje linie wejściowe wyglądają tak:
Zatem naprawdę nie ma tego powodu:
Robisz wiele podciągów, aby uzyskać coś, co wygląda ... dokładnie tak, jak już wygląda w pliku. Co powiesz na to?
To tylko chwyta pierwsze 10 znaków z linii. Możesz także zrezygnować
bash
całkowicie i po prostu użyćawk
:Pobiera to datę
$1
(pierwsza kolumna oddzielona spacjami w każdym wierszu) i wykorzystuje ją do wygenerowania nazwy pliku.Zauważ, że możliwe jest, że w twoich plikach znajdują się fałszywe linie logów. Oznacza to, że problem może dotyczyć danych wejściowych, a nie skryptu. Możesz rozszerzyć
awk
skrypt, aby oznaczyć fałszywe linie w następujący sposób:Spowoduje to zapisanie wierszy pasujących
YYYY-MM-DD
do plików dziennika i oznaczenie wierszy, które nie zaczynają się od znacznika czasu na standardowym wyjściu.źródło
cut -c 1-10 file.log | uniq -c
daje oczekiwany wynik. Używam,${line:0:4}-${line:5:2}-${line:8:2}
ponieważ umieszczę plik w katalogu${line:0:4}/${line:5:2}/${line:8:2}
i uprościłem problem (zaktualizuję opis problemu). Wiem, żeawk
może mi tutaj pomóc, ale miałem inne problemy z korzystaniem z niego. Chcę zrozumieć problembash
, a nie znaleźć alternatywnych rozwiązań.cut
stwierdzenie, które działa. Ponieważ chcę porównać jabłka z jabłkami, a nie z pomarańczami, muszę uczynić rzeczy tak podobnymi, jak to możliwe.Wygląda na to, że chcesz:
close
Utrzymuje otwartą tabelę plików z napełnieniem.źródło