Como acrescentar Linha à Linha anterior?

9

Eu tenho um arquivo de log que precisa ser analisado e analisado. O arquivo contém algo semelhante como abaixo:

Arquivo:

20141101 server contain dump
20141101 server contain nothing
    {uekdmsam ikdas 

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

Com base no cenário acima, tenho que verificar se a linha inicial não contém data ou número que tenho que acrescentar à linha anterior.

Arquivo de saída:

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

Respostas:

11

Uma versão em perl, usando lookaheads negativos:

$ 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

-0permite que o regex seja correspondido em todo o arquivo e \n(?!([0-9]{8}|$))tenha uma aparência negativa, o que significa uma nova linha não seguida por 8 dígitos ou no final da linha (que, com -0, será o final do arquivo).

muru
fonte
@terdon, atualizado para salvar a última nova linha.
Muru
Agradável! Gostaria de te dar um
voto positivo
Não, -0se for para registros delimitados por NUL. Use -0777para armazenar o arquivo inteiro na memória (o que você não precisa aqui).
Stéphane Chazelas
@ StéphaneChazelas Então, qual é a melhor maneira de fazer o Perl corresponder à nova linha, além de ler o arquivo inteiro?
Muru
Veja as outras respostas que processam o arquivo linha por linha.
Stéphane Chazelas
5

Pode ser um pouco fácil com sed

sed -e ':1 ; N ; $!b1' -e 's/\n\+\( *[^0-9]\)/\1/g'
  • primeira parte :1;N;$!b1coletar todas as linhas no arquivo dividido por \nem 1 linha longa

  • a segunda parte tira o símbolo de nova linha se seguir o símbolo de não dígito com possíveis espaços entre eles.

Para evitar limitação de memória (especialmente para arquivos grandes), você pode usar:

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

Ou esqueça um sedroteiro difícil e lembre-se de que o ano começa em2

tr '\n2' ' \n' | sed -e '1!s/^/2/' -e 1{/^$/d} -e $a
Costas
fonte
Bom, +1. Você poderia adicionar uma explicação de como funciona, por favor?
terdon
1
Aw. Agradável. Eu sempre faço a tr '\n' $'\a' | sed $'s/\a\a*\( *[^0-9]\)/\1/g' | tr $'\a' '\n'mim mesmo.
mirabilos
Desculpe, porém, você tem que fazer um voto negativo por usar coisas que não são POSIX BASIC REGULAR EXPRESSION S no sed (1) , que é um GNUism.
mirabilos
1
@Costas, essa é a página de manual do GNU grep. As especificações POSIX BRE estão . BRE equivalente a ERE +é \{1,\}. [\n]também não é portátil. \n\{1,\}seria POSIX.
Stéphane Chazelas
1
Além disso, você não pode ter outro comando após um rótulo. : 1;xé definir o 1;xrótulo nos POSIX seds. Então, você precisa: sed -e :1 -e 'N;$!b1' -e 's/\n\{1,\}\( *[^0-9]\)/\1/g'. Observe também que muitas sedimplementações têm um pequeno limite no tamanho de seu espaço padrão (o POSIX garante apenas 10 x LINE_MAX IIRC).
Stéphane Chazelas
5

Uma maneira seria:

 $ 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

No entanto, também remove a nova linha final. Para adicioná-lo novamente, use:

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

Explicação

O -lremoverá as novas linhas à direita (e também adicionará uma a cada printchamada, motivo pelo qual eu uso isso printf. Em seguida, se a linha atual começar com números ( /^\d+/) e o número da linha atual for maior que um ( $.>1isso é necessário para evitar adicionar mais linha vazia no início), adicione \na ao início da linha e printfimprime cada linha.


Como alternativa, você pode alterar todos os \ncaracteres para e \0, em seguida, alterar os \0que estão corretos antes de uma sequência de números para \nnovamente:

$ 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

Para fazer corresponder apenas cadeias de 8 números, use este:

$ tr '\n' '\0' < file | perl -pe 's/\0\d{8} |$/\n$&/g' | tr -d '\0'
terdon
fonte
O primeiro argumento para printfé o formato . Useprintf "%s", $_
Stéphane Chazelas
@ StéphaneChazelas por quê? Quero dizer, eu sei que é mais limpo e talvez mais fácil de entender, mas existe algum perigo que isso proteja?
terdon
Sim, é errado e potencialmente perigoso se a entrada puder conter% caracteres. Tente com uma entrada com, %10000000000spor exemplo.
Stéphane Chazelas
Em C, essa é uma prática muito conhecida e uma fonte de vulnerabilidade muito ruim. Com perl, echo %.10000000000f | perl -ne printftraz minha máquina de joelhos.
Stéphane Chazelas
@ StéphaneChazelas uau, sim. Meu também. É justo, então, responda editado e obrigado.
terdon
3

Tente fazer isso usando o :

#!/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
}

Para usá-lo:

chmod +x script.awk
./script.awk file.txt
Gilles Quenot
fonte
2

Outra maneira mais simples (do que a minha outra resposta) usando o algoritmo e terdon :

awk 'NR>1 && /^[0-9]{8}/{printf "%s","\n"$0;next}{printf "%s",$0}END{print}' file
Gilles Quenot
fonte
ITYM END{print ""}. Alternativa:awk -v ORS= 'NR>1 && /^[0-9]{8}/{print "\n"};1;END{print "\n"}'
Stéphane Chazelas
1
sed -e:t -e '$!N;/\n *[0-9]{6}/!s/\n */ /;tt' -eP\;D
mikeserv
fonte
0

O programa no bash:

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

no formato de uma linha:

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

Solução com preservação de barras invertidas ( read -r) e espaços à esquerda (logo IFS=após 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

formulário de uma linha:

while IFS= read -r L; do if [[ $L =~ ^[0-9]{8} ]]; then echo; echo -nE "${L} "; else echo -nE "${L} "; fi done < file.text
torre
fonte
Isso será interrompido se a linha contiver, digamos, uma barra invertida e um n. Ele também remove os espaços em branco. Mas você pode usar mkshpara fazer isso: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
Claro que não é para tudo algoritmo, mas solução para os requisitos fornecidos pela tarefa. É claro que a solução final será mais complexo e menos legível num relance como geralmente acontece na vida real :)
torre
Concordo, mas aprendi da maneira mais difícil a não assumir muito sobre o OP, especialmente se eles substituirem o texto real por texto fictício.
mirabilos
0
[shyam@localhost ~]$ perl -lne 's/^/\n/ if $.>1 && /^\d+/; printf "%s",$_' appendDateText.txt

isso vai funcionar

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 
Shyam Gupta
fonte