Jak dołączyć linię do poprzedniej linii?


9

Mam plik dziennika, który należy przeanalizować i przeanalizować. Plik zawiera coś podobnego jak poniżej:

Plik:

20141101 server contain dump
20141101 server contain nothing
    {uekdmsam ikdas 

jwdjamc ksadkek} ssfjddkc * kdlsdl
sddsfd jfkdfk 
20141101 server contain dump

W oparciu o powyższy scenariusz muszę sprawdzić, czy wiersz początkowy nie zawiera daty ani numeru, który muszę dołączyć do poprzedniego wiersza.

Plik wyjściowy:

20141101 server contain dump
20141101 server contain nothing {uekdmsam ikdas jwdjamc ksadkek} ssfjddkc * kdlsdl sddsfd jfkdfk 
20141101 server contain dump

Odpowiedzi:


11

Wersja w perl, wykorzystująca negatywne oczekiwania:

$ perl -0pe 's/\n(?!([0-9]{8}|$))//g' test.txt
20141101 server contain dump
20141101 server contain nothing    {uekdmsam ikdas jwdjamc ksadkek} ssfjddkc * kdlsdlsddsfd jfkdfk
20141101 server contain dump

-0pozwala na dopasowanie wyrażenia regularnego w całym pliku i \n(?!([0-9]{8}|$))jest przeczącym przeczeniem, co oznacza, że ​​po nowej linii nie ma 8 cyfr lub końca linii (która, z -0, będzie końcem pliku).


@terdon, zaktualizowano, aby zapisać ostatnią nową linię.
muru

Niezłe! Głosowałbym za tobą, ale obawiam się, że już miałem :)
terdon

Nie, -0jeśli dotyczy rekordów rozdzielanych przez NUL. Użyj, -0777aby skasować cały plik w pamięci (którego nie musisz tutaj).
Stéphane Chazelas

@ StéphaneChazelas Więc jaki jest najlepszy sposób, aby Perl pasował do nowej linii, poza wczytaniem całego pliku?
muru

Zobacz inne odpowiedzi przetwarzające plik linia po linii.
Stéphane Chazelas

5

Może być trochę łatwiejsze sed

sed -e ':1 ; N ; $!b1' -e 's/\n\+\( *[^0-9]\)/\1/g'
  • pierwsza część :1;N;$!b1zbierz wszystkie linie w pliku podzielone przez \n1 długą linię

  • druga część usuń symbol nowej linii, jeśli występuje po symbolu innym niż cyfrowy, z możliwymi odstępami między nim.

Aby uniknąć ograniczenia pamięci (szczególnie w przypadku dużych plików), możesz użyć:

sed -e '1{h;d}' -e '1!{/^[0-9]/!{H;d};/^[0-9]/x;$G}' -e 's/\n\+\( *[^0-9]\)/\1/g'

Albo zapomnij o trudnych sedskryptach i pamiętaj, że rok zaczyna się od2

tr '\n2' ' \n' | sed -e '1!s/^/2/' -e 1{/^$/d} -e $a

Fajnie, +1. Czy możesz dodać wyjaśnienie, jak to działa?
terdon

1
Aw. Miły. Zawsze robię tr '\n' $'\a' | sed $'s/\a\a*\( *[^0-9]\)/\1/g' | tr $'\a' '\n'siebie.
mirabilos

Przepraszam, ale muszę zrewanżować się za używanie rzeczy, które nie są POSIX PODSTAWOWEJ REGULARNEJ EKSPRESJI S w sed (1) , która jest GNUizmem.
mirabilos

1
@Casas, to jest strona podręcznika GNU grep. POSIX BRE Spec są tam . Odpowiednikiem BRE dla ERE +jest \{1,\}. [\n]też nie jest przenośny. \n\{1,\}byłby POSIX.
Stéphane Chazelas

1
Ponadto nie można mieć innego polecenia po etykiecie. : 1;xjest zdefiniowanie 1;xetykiety w zestawach POSIX. Więc trzeba: sed -e :1 -e 'N;$!b1' -e 's/\n\{1,\}\( *[^0-9]\)/\1/g'. Należy również pamiętać, że wiele sedimplementacji ma niewielkie ograniczenie wielkości przestrzeni wzorcowej (POSIX gwarantuje tylko 10 x LINE_MAX IIRC).
Stéphane Chazelas

5

Jednym ze sposobów byłoby:

 $ perl -lne 's/^/\n/ if $.>1 && /^\d+/; printf "%s",$_' file
 20141101 server contain dump
 20141101 server contain nothing    {uekdmsam ikdas jwdjamc ksadkek} ssfjddkc * kdlsdlsddsfd jfkdfk 
 20141101 server contain dump

Jednak .that usuwa również ostatnią nową linię. Aby dodać go ponownie, użyj:

$ { perl -lne 's/^/\n/ if $.>1 && /^\d+/; printf "%s",$_' file; echo; } > new

Wyjaśnienie

-lUsunie końcowe znaki nowej linii (a także dodasz do każdej printrozmowy, dlatego używam printfzamiast. Następnie, jeśli obecne rozpoczyna linia z liczb ( /^\d+/) oraz numer bieżącego wiersza jest większy niż jeden ( $.>1jest to konieczne, aby uniknąć dodając dodatkowy pusta linia na początku), dodaj a \nna początku linii. printfDrukuje każdą linię.


Alternatywnie możesz zmienić wszystkie \nznaki na \0, a następnie zmienić te, \0które znajdują się tuż przed ciągiem liczb, aby \nponownie:

$ tr '\n' '\0' < file | perl -pe 's/\0\d+ |$/\n$&/g' | tr -d '\0'
20141101 server contain dump
20141101 server contain nothing    {uekdmsam ikdas jwdjamc ksadkek} ssfjddkc * kdlsdlsddsfd jfkdfk 
20141101 server contain dump

Aby dopasować tylko ciągi 8 cyfr, użyj tego zamiast tego:

$ tr '\n' '\0' < file | perl -pe 's/\0\d{8} |$/\n$&/g' | tr -d '\0'

Pierwszym argumentem printfjest format . Użyjprintf "%s", $_
Stéphane Chazelas

@ StéphaneChazelas dlaczego? Mam na myśli, że wiem, że jest czystszy i być może łatwiejszy do zrozumienia, ale czy istnieje jakieś niebezpieczeństwo, przed którym by to chroniło?
terdon

Tak, jest błędny i potencjalnie niebezpieczny, jeśli dane wejściowe mogą zawierać% znaków. Spróbuj %10000000000sna przykład z danymi wejściowymi .
Stéphane Chazelas

W języku C jest to bardzo dobrze znane źródło bardzo złych praktyk i podatności na ataki. Z perl, echo %.10000000000f | perl -ne printfsprowadza moją maszynę na kolana.
Stéphane Chazelas

@ StéphaneChazelas wow, tak. Moje też. W takim razie wystarczy, odpowiedz edytowane i dziękuję.
terdon

3

Spróbuj to zrobić za pomocą :

#!/usr/bin/awk -f

{
    # if the current line begins with 8 digits followed by
    # 'nothing' OR the current line doesn't start with 8 digits
    if (/^[0-9]{8}.*nothing/ || !/^[0-9]{8}/) {
        # print current line without newline
        printf "%s", $0
        # feeding a 'state' variable
        weird=1
    }
    else {
        # if last line was treated in the 'if' statement
        if (weird==1) {
            printf "\n%s", $0
            weird=0
        }
        else {
            print # print the current line
        }
    }
}
END{
    print # add a newline when there's no more line to treat
}

Aby go użyć:

chmod +x script.awk
./script.awk file.txt

2

Kolejny najprostszy sposób (niż moja inna odpowiedź) przy użyciu algorytmu i terdon :

awk 'NR>1 && /^[0-9]{8}/{printf "%s","\n"$0;next}{printf "%s",$0}END{print}' file

ITYM END{print ""}. Alternatywnie:awk -v ORS= 'NR>1 && /^[0-9]{8}/{print "\n"};1;END{print "\n"}'
Stéphane Chazelas


0

Le program en bash:

while read LINE
do
    if [[ $LINE =~ ^[0-9]{8} ]]
    then
        echo -ne "\n${LINE} "
    else
        echo -n "${LINE} "
    fi
done < file.txt

w formie jednego wiersza:

while read L; do if [[ $L =~ ^[0-9]{8} ]]; then echo -ne "\n${L} "; else echo -n "${L} "; fi done < file.txt

Rozwiązanie z zachowaniem ukośników odwrotnych ( read -r) i spacjami wiodącymi (zaraz IFS=po while):

while IFS= read -r LINE
do
    if [[ $LINE =~ ^[0-9]{8} ]]
    then
        echo
        echo -nE "\n${LINE} "
    else
        echo -nE "${LINE} "
    fi
done < file.txt

formularz jednowierszowy:

while IFS= read -r L; do if [[ $L =~ ^[0-9]{8} ]]; then echo; echo -nE "${L} "; else echo -nE "${L} "; fi done < file.text

To się zepsuje, jeśli linia zawiera, powiedzmy, odwrotny ukośnik i znak n. Usuwa również białe znaki. Ale możesz mkshto zrobić:while IFS= read -r L; do [[ $L = [0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]* ]] && print; print -nr -- "$L"; done; print
mirabilos

Oczywiście nie jest to algorytm wszystkiego, ale rozwiązanie dla wymagań dostarczonych przez zadanie. Oczywiście ostateczne rozwiązanie będzie na pierwszy rzut oka bardziej złożone i mniej czytelne, jak to zwykle bywa w prawdziwym życiu :)
wieża

Zgadzam się, ale nauczyłem się, jak nie zakładać zbyt wiele o OP ☺, zwłaszcza jeśli zastępują one tekst tekstem pozornym.
mirabilos

0
[shyam@localhost ~]$ perl -lne 's/^/\n/ if $.>1 && /^\d+/; printf "%s",$_' appendDateText.txt

to zadziała

i/p:
##06/12/2016 20:30 Test Test Test
##TestTest
##06/12/2019 20:30 abbs  abcbcb abcbc
##06/11/2016 20:30 test test
##i123312331233123312331233123312331233123312331233Test
## 06/12/2016 20:30 abc

o/p:
##06/12/2016 20:30 Test Test TestTestTest
##06/12/2019 20:30 abbs  abcbcb abcbc
##06/11/2016 20:30 test ##testi123312331233123312331233123312331233123312331233Test
06/12/2016 20:30 abc vi appendDateText.txt 
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.