2017-06-30 49 views
5

Mam plik dziennika (od klienta). 18 występów. Cała zawartość pliku jest w 1 linii. Chcę przeczytać plik w logstash. Ale mam problemy z powodu pamięci. Plik jest czytany wiersz po wierszu, ale niestety wszystko jest w jednym wierszu.Zastąpić każdy} znacznikiem} n ogromnym (12 GB), który składa się z 1 linii?

próbowałem podzielić plik na linie tak, że logstash można go (plik ma prostą formacie JSON, brak obiektów zagnieżdżonych) przetwarzać chciałem mieć każdy json w jednej linii, rozbitego na } przez zastąpienie }\n:

sed -i 's/}/}\n/g' NonPROD.log.backup 

Ale sed jest zabity - zakładam również z powodu pamięci. Jak mogę to rozwiązać? Czy mogę pozwolić na manipulowanie plikiem przy użyciu innych porcji danych niż linie? Domyślnie sed czyta wiersz po wierszu.

+2

Można rozważyć użycie 'jq' przetworzyć plik w modzie streamingu. – chepner

Odpowiedz

6

Poniższa używa tylko funkcjonalność wbudowany w powłoce:

#!/bin/bash 

# as long as there exists another } in the file, read up to it... 
while IFS= read -r -d '}' piece; do 
    # ...and print that content followed by '}' and a newline. 
    printf '%s}\n' "$piece" 
done 

# print any trailing content after the last } 
[[ $piece ]] && printf '%s\n' "$piece" 

Jeśli logstash skonfigurowany do odczytu z portu TCP (używając 14321 jako arbitralne przykładzie poniżej), można uruchomić thescript <NonPROD.log.backup >"/dev/tcp/127.0.0.1/14321" lub podobne, tam jesteś - bez potrzeby podwożenia swojej oryginalnej przestrzeni pliku wejściowego dostępnej na dysku, jak wymagają inne inne odpowiedzi.

1

Mogłabyś:

  1. podzielić plik powiedzieć kawałki 1M korzystając split -b 1m file.log
  2. przetwarzać wszystkie pliki sed 's/}/}\n/g' x*
  3. ... i przekierować wyjście sed aby połączyć je z powrotem do jednego kawałka

Wadą tego jest podwojona przestrzeń do przechowywania.

+1

Zostanie podwojona pamięć bez względu na to, czy chcemy serializować wyjście na dysk, a wykonywana transformacja generuje wyjście większe niż oryginalne wejście - podczas gdy 'sed -i' pisze tymczasowe wyjście, to wyjście musi iść * gdzieś*. Można dokonać prawdziwie transformacji w miejscu tylko wtedy, gdy wynik jest równy lub mniejszy. Jeśli skasowałeś oryginalny plik natychmiast po wykonaniu 'split', możesz uniknąć przekroczenia podwójnej + 1mb (plus same dodatkowe dodatkowe linie). –

+0

(...no cóż - "only" było trochę mocne - Linux ma pewne rozszerzenia, które pozwalają retroaktywnie usuwać określone bloki w pliku odwzorowanym w pamięci, a jeśli są używane z odpowiednim systemem plików, są skąpe, ale jest to kwestia narożna). –

2

Można uruchomić go poprzez tr, a następnie umieścić z powrotem na wspornik końcowy na końcu każdej linii: tylko

$ cat NonPROD.log.backup | tr '}' '\n' | sed 's/$/}/' > tmp$$ 
$ wc -l NonPROD.log.backup tmp$$ 
    0 NonPROD.log.backup 
    43 tmp10528 
    43 total 

(Mój plik testowy miał 43 wsporników.)

+0

Proste, ale działa (z wyjątkiem wstawiania dodatkowego '}' na końcu pliku, jeśli już go tam nie ma). Sugerowałbym usunięcie 'cat' z potoku -'

+0

Zmęczony - zmniejszył się o 1%. – Jack

+1

Yup. Będzie to znacznie większa różnica, jeśli program jest czymś w rodzaju "sortowania", które może zrównoleglić, gdy otrzyma dostrzegalny uchwyt pliku, a nie musi czytać od przodu do tyłu za pomocą FIFO. ('cat file |' nie zapewnia bezpośredniego dostępu do 'file', ale tylko do streamowanego wyjścia z' cat'). Innym przykładem jest 'wc -c' - z prawdziwym uchwytem pliku może on kończyć się w stałym czasie, bez względu na rozmiar pliku, z FIFO musi czytać całość bezwarunkowo. –

0

inna alternatywa z fold

$ fold -w 1000 long_line_file | sed 's/}/}\n\n/g' | tr -s '\n' 
+0

Otrzymujesz nowe linie w miejscach, gdzie ich nie potrzebujesz. –

3

Z GNU awk dla RT:

$ printf 'abc}def}ghi\n' | awk -v RS='}' '{ORS=(RT?"}\n":"")}1' 
abc} 
def} 
ghi 

z innymi implementacjami awk:

$ printf 'abc}def}ghi\n' | awk -v RS='}' -v ORS='}\n' 'NR>1{print p} {p=$0} END{printf "%s",p}' 
abc} 
def} 
ghi 

postanowiłem przetestować wszystkie aktualnie zamieszczonych rozwiązań dla funkcji i czasu wykonania przy użyciu pliku wejściowego e generowane przez komendę:

awk 'BEGIN{for(i=1;i<=1000000;i++)printf "foo}"; print "foo"}' > file1m 

i oto co mam:

skrypty

1) awk (zarówno awk powyżej miały podobne wyniki):

time awk -v RS='}' '{ORS=(RT?"}\n":"")}1' file1m 

Got oczekiwany wynik, czas =

real 0m0.608s 
user 0m0.561s 
sys  0m0.045s 

2) shell loop:

$ cat tst.sh 
#!/bin/bash 

# as long as there exists another } in the file, read up to it... 
while IFS= read -r -d '}' piece; do 
    # ...and print that content followed by '}' and a newline. 
    printf '%s}\n' "$piece" 
done 

# print any trailing content after the last } 
[[ $piece ]] && printf '%s\n' "$piece" 

$ time ./tst.sh < file1m 

Posiadanie oczekiwany wynik, czas =

real 1m52.152s 
user 1m18.233s 
sys  0m32.604s 

3) tr+sed:

$ time tr '}' '\n' < file1m | sed 's/$/}/' 

nie przyniosły oczekiwanego wyjściowego (Dodano niepożądany } na końcu pliku), termin =

real 0m0.577s 
user 0m0.468s 
sys  0m0.078s 

Z wariację usunąć ten ostatni niepożądane }:

$ time tr '}' '\n' < file1m | sed 's/$/}/; $s/}//' 

real 0m0.718s 
user 0m0.670s 
sys  0m0.108s 

4) fold+sed+tr:

$ time fold -w 1000 file1m | sed 's/}/}\n\n/g' | tr -s '\n' 

Posiadanie oczekiwany wynik, czas =

real 0m0.811s 
user 0m1.137s 
sys  0m0.076s 

5) split+sed+cat:

$ cat tst2.sh 
mkdir tmp$$ 
pwd="$(pwd)" 
cd "tmp$$" 
split -b 1m "${pwd}/${1}" 
sed -i 's/}/}\n/g' x* 
cat x* 
rm -f x* 
cd "$pwd" 
rmdir tmp$$ 

$ time ./tst2.sh file1m 

Got oczekiwany wynik, czas =

real 0m0.983s 
user 0m0.685s 
sys  0m0.167s