Come aggiungere la riga alla riga precedente?


9

Ho un file di registro che deve essere analizzato e analizzato. Il file contiene qualcosa di simile come di seguito:

File:

20141101 server contain dump
20141101 server contain nothing
    {uekdmsam ikdas 

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

Sulla base dello scenario di cui sopra, devo verificare se la riga iniziale non contiene la data o il numero che devo aggiungere alla riga precedente.

File di uscita:

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

Risposte:


11

Una versione in perl, usando lookahead negativi:

$ 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

-0consente al regex di essere abbinato su tutto il file ed \n(?!([0-9]{8}|$))è un lookahead negativo, il che significa che una nuova riga non è seguita da 8 cifre, o la fine della linea (che, con -0, sarà la fine del file).


@terdon, aggiornato per salvare l'ultima riga.
muru,

Ben fatto! Ti avrei votato ma temo di averlo già fatto :)
terdon

No, -0se per i record delimitati da NUL. Utilizzare -0777per slurpare l'intero file in memoria (che non è necessario qui).
Stéphane Chazelas,

@ StéphaneChazelas Qual è il modo migliore per far corrispondere Perl alla nuova riga, oltre a leggere l'intero file?
Muru,

Vedi le altre risposte che elaborano il file riga per riga.
Stéphane Chazelas,

5

Potrebbe essere un po 'facile con sed

sed -e ':1 ; N ; $!b1' -e 's/\n\+\( *[^0-9]\)/\1/g'
  • la prima parte :1;N;$!b1raccoglie tutte le righe nel file divise per \n1 linea lunga

  • la seconda parte rimuove il simbolo di nuova riga se seguiva un simbolo non cifra con possibili spazi tra i suoi.

Per evitare la limitazione della memoria (specialmente per file di grandi dimensioni) è possibile utilizzare:

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

Oppure dimentica un sedcopione difficile e ricordare che l'anno inizia2

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

Bello, +1. Potresti aggiungere una spiegazione di come funziona per favore?
terdon

1
Aw. Bello. Faccio sempre tr '\n' $'\a' | sed $'s/\a\a*\( *[^0-9]\)/\1/g' | tr $'\a' '\n'me stesso.
mirabilos,

Scusate, devo sottovalutare però per usare cose che non sono POSIX BASIC REGOLARE ESPRESSIONE S in sed (1) , che è un GNUismo.
mirabilos,

1
@Costas, questa è la pagina man di GNU grep. Ci sono le specifiche POSIX BRE . BRE equivalente di ERE +è \{1,\}. [\n]non è neanche portatile. \n\{1,\}sarebbe POSIX.
Stéphane Chazelas,

1
Inoltre, non puoi avere un altro comando dopo un'etichetta. : 1;xè definire l' 1;xetichetta in sed POSIX. Quindi è necessario: sed -e :1 -e 'N;$!b1' -e 's/\n\{1,\}\( *[^0-9]\)/\1/g'. Si noti inoltre che molte sedimplementazioni hanno un piccolo limite sulla dimensione del loro spazio di pattern (POSIX garantisce solo 10 x LINE_MAX IIRC).
Stéphane Chazelas,

5

Un modo sarebbe:

 $ 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

Tuttavia, ciò rimuove anche la nuova riga finale. Per aggiungerlo di nuovo, utilizzare:

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

Spiegazione

Il -lrimuoverà a capo finale (e anche aggiungere uno ad ogni printchiamata è per questo che io uso printfinvece. Poi, se la linea corrente inizia con numeri ( /^\d+/) e il numero di riga corrente è maggiore di uno ( $.>1questo è necessario per evitare di aggiungere un extra riga vuota all'inizio), aggiungi \na all'inizio della riga e printfstampa ogni riga.


In alternativa, puoi cambiare tutti i \ncaratteri in \0, quindi cambiare quelli \0che sono proprio prima di una stringa di numeri in \n:

$ 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

Per far corrispondere solo le stringhe di 8 numeri, utilizzare invece questo:

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

Il primo argomento printfè il formato . Usaprintf "%s", $_
Stéphane Chazelas il

@ StéphaneChazelas perché? Voglio dire, so che è più pulito e forse più facile da capire, ma c'è qualche pericolo che possa proteggere?
terdon

Sì, è sbagliato e potenzialmente pericoloso se l'input può contenere% caratteri. Prova con un input con %10000000000sad esempio.
Stéphane Chazelas,

In C, questa è una pessima pratica molto nota e fonte di vulnerabilità. Con perl, echo %.10000000000f | perl -ne printfmette la mia macchina in ginocchio.
Stéphane Chazelas,

@ StéphaneChazelas wow, sì. Anche il mio. Abbastanza giusto quindi, risposta modificata e grazie.
terdon

3

Prova a farlo usando :

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

Per usarlo:

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

2

Un altro modo più semplice (rispetto alla mia altra risposta) usando l' algoritmo di e terdon :

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

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

0

Il programma è bash:

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

in una riga:

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

Soluzione con backslash preserving ( read -r) e spazi iniziali (subito IFS=dopo 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

modulo a una riga:

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

Questo si interromperà se la linea contiene, diciamo, una barra rovesciata e un n. Elimina anche gli spazi bianchi. Ma puoi usare mkshper fare questo: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,

Ovviamente non è per tutto l'algoritmo, ma la soluzione per i requisiti forniti dall'attività. Naturalmente la soluzione finale sarà più complessa e meno leggibile a colpo d'occhio come accade di solito nella vita reale :)
torre

Sono d'accordo, ma ho imparato a malapena a non dare troppa importanza all'OP, specialmente se sostituiscono il testo effettivo con testo fittizio.
mirabilos,

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

funzionerà

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 
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.