2016-01-25 27 views
6

Ho una preferenza per i comandi della shell per fare le cose. Ho un file molto, molto grande - circa 2,8 GB e il contenuto è quello di JSON. Tutto è su una riga e mi è stato detto che ci sono almeno 1,5 milioni di record.Cerca e sostituisci stringa in un file molto grande

Devo preparare il file per il consumo. Ogni record deve essere sulla propria linea. Esempio:

{"RomanCharacters":{"Alphabet":[{"RecordId":"1",...]},{"RecordId":"2",...},{"RecordId":"3",...},{"RecordId":"4",...},{"RecordId":"5",...} }} 

In alternativa, utilizzare il seguente ...

{"Accounts":{"Customer":[{"AccountHolderId":"9c585258-c94c-442b-a2f0-1ebbcc274795","Title":"Mrs","Forename":"Tina","Surname":"Wright","DateofBirth":"1988-01-01","Contact":[{"Contact_Info":"9168777943","TypeId":"Mobile Number","PrimaryFlag":"No","Index":"1","Superseded":"No" },{"Contact_Info":"9503588153","TypeId":"Home Telephone","PrimaryFlag":"Yes","Index":"2","Superseded":"Yes" },{"Contact_Info":"[email protected]","TypeId":"Email Address","PrimaryFlag":"No","Index":"3","Superseded":"No" },{"Contact_Info":"[email protected]","TypeId":"Email Address","PrimaryFlag":"Yes","Index":"4","Superseded":"Yes" }, {"Contact_Info":"[email protected]","TypeId":"Email Address","PrimaryFlag":"No","Index":"5","Superseded":"NO" },{"Contact_Info":"15482475584","TypeId":"Mobile_Phone","PrimaryFlag":"No","Index":"6","Superseded":"No" }],"Address":[{"AddressPtr":"5","Line1":"Flat No.14","Line2":"Surya Estate","Line3":"Baner","Line4":"Pune ","Line5":"new","Addres_City":"pune","Country":"India","PostCode":"AB100KP","PrimaryFlag":"No","Superseded":"No"},{"AddressPtr":"6","Line1":"A-602","Line2":"Viva Vadegiri","Line3":"Virar","Line4":"new","Line5":"banglow","Addres_City":"Mumbai","Country":"India","PostCode":"AB10V6T","PrimaryFlag":"Yes","Superseded":"Yes"}],"Account":[{"Field_A":"6884133655531279","Field_B":"887.07","Field_C":"A Loan Product",...,"FieldY_":"2015-09-18","Field_Z":"24275627"}]},{"AccountHolderId":"92a5788f-cd8f-423d-ae5f-4eb0ceb457fd","_Title":"Dr","_Forename":"Christopher","_Surname":"Carroll","_DateofBirth":"1977-02-02","Contact":[{"Contact_Info":"9168777943","TypeId":"Mobile Number","PrimaryFlag":"No","Index":"7","Superseded":"No" },{"Contact_Info":"9503588153","TypeId":"Home Telephone","PrimaryFlag":"Yes","Index":"8","Superseded":"Yes" },{"Contact_Info":"[email protected]","TypeId":"Email Address","PrimaryFlag":"No","Index":"9","Superseded":"No" },{"Contact_Info":"[email protected]","TypeId":"Email Address","PrimaryFlag":"Yes","Index":"10","Superseded":"Yes" }],"Address":[{"AddressPtr":"11","Line1":"Flat No.14","Line2":"Surya Estate","Line3":"Baner","Line4":"Pune ","Line5":"new","Addres_City":"pune","Country":"India","PostCode":"AB11TXF","PrimaryFlag":"No","Superseded":"No"},{"AddressPtr":"12","Line1":"A-602","Line2":"Viva Vadegiri","Line3":"Virar","Line4":"new","Line5":"banglow","Addres_City":"Mumbai","Country":"India","PostCode":"AB11O8W","PrimaryFlag":"Yes","Superseded":"Yes"}],"Account":[{"Field_A":"4121879819185553","Field_B":"887.07","Field_C":"A Loan Product",...,"Field_X":"2015-09-18","Field_Z":"25679434"}]},{"AccountHolderId":"4aa10284-d9aa-4dc0-9652-70f01d22b19e","_Title":"Dr","_Forename":"Cheryl","_Surname":"Ortiz","_DateofBirth":"1977-03-03","Contact":[{"Contact_Info":"9168777943","TypeId":"Mobile Number","PrimaryFlag":"No","Index":"13","Superseded":"No" },{"Contact_Info":"9503588153","TypeId":"Home Telephone","PrimaryFlag":"Yes","Index":"14","Superseded":"Yes" },{"Contact_Info":"[email protected]","TypeId":"Email Address","PrimaryFlag":"No","Index":"15","Superseded":"No" },{"Contact_Info":"[email protected]","TypeId":"Email Address","PrimaryFlag":"Yes","Index":"16","Superseded":"Yes" }],"Address":[{"AddressPtr":"17","Line1":"Flat No.14","Line2":"Surya Estate","Line3":"Baner","Line4":"Pune ","Line5":"new","Addres_City":"pune","Country":"India","PostCode":"AB12SQR","PrimaryFlag":"No","Superseded":"No"},{"AddressPtr":"18","Line1":"A-602","Line2":"Viva Vadegiri","Line3":"Virar","Line4":"new","Line5":"banglow","Addres_City":"Mumbai","Country":"India","PostCode":"AB12BAQ","PrimaryFlag":"Yes","Superseded":"Yes"}],"Account":[{"Field_A":"3288214945919484","Field_B":"887.07","Field_C":"A Loan Product",...,"Field_Y":"2015-09-18","Field_Z":"66264768"}]}]}} 

risultato finale dovrebbe essere:

{"RomanCharacters":{"Alphabet":[{"RecordId":"1",...]}, 
{"RecordId":"2",...}, 
{"RecordId":"3",...}, 
{"RecordId":"4",...}, 
{"RecordId":"5",...} }} 

comandi Tentativo:

  • sed -e 's/,{"RecordId"/}]},\n{"RecordId"/g' sample.dat
  • awk '{gsub(",{\"RecordId\"",",\n{\"RecordId\"",$0); print $0}' sample.dat

I comandi tentati funzionano perfettamente per file di piccole dimensioni. Ma non funziona per il file da 2,8 GB che devo manipolare. Sed si ferma a metà strada dopo 10 minuti senza motivo e non è stato fatto nulla. Awk ha errato con un motivo di Segmentation Fault (core dump) dopo molte ore. Ho provato la ricerca e la sostituzione di perl e ho ricevuto un errore che diceva "Memoria insufficiente".

Qualsiasi aiuto/idee sarebbe fantastico!

Ulteriori informazioni sulla mia macchina:

  • Più di 105 GB di spazio su disco disponibile.
  • 8 GB memoria
  • 4 core CPU
  • esecuzione Ubuntu 14,04
+0

Abbiamo bisogno di dati campione migliori - non deve essere una copia dei dati, ma illustrativo del problema in questione. Inoltre, hai preso in considerazione l'utilizzo di un parser? – Sobrique

+1

Il problema di base, penso, è che tutti e 3 di questi strumenti leggono una linea alla volta e quindi vengono colpiti dalla "singola linea enorme". Prova prima la pre-elaborazione con qualcosa come 'tr ',' \ 012'' per sostituire le virgole con le nuove linee. Quindi gli strumenti line-at-a-time funzioneranno meglio. –

+0

Riprovare con perl ma impostare $/su ",". Prova anche il parametro "-u" per sed (--unbuffered). – neuhaus

risposta

3

Per quanto riguarda perl: Prova a impostare il separatore di linea di ingresso $/-}, in questo modo:

#!/usr/bin/perl 
$/= "},"; 
while (<>){ 
    print "$_\n"; 
}' 

o, come una battuta:

$ perl -e '$/="},";while(<>){print "$_\n"}' sample.dat 
+1

Ciò ha dimostrato di funzionare rapidamente. Ho modificato il tuo script per aver trovato la soluzione al problema. Lo condividerò a breve. – dat789

+0

Potrebbe essere utile dare un'occhiata ai flag '-n' e' -p'. Penso che potresti scriverlo come: 'perl -pe 'BEGIN {$/="}, "} print" \ n "; } ' – Sobrique

2

Prova utilizzando } come il separatore di record, ad esempio in Perl:

perl -l -0175 -ne 'print $_, $/' < input 

Potrebbe essere necessario incollare di nuovo le linee che contengono solo }.

2

Ciò evita il problema della memoria non guardando i dati come un singolo record, ma può andare troppo lontano nell'altro senso rispetto alle prestazioni (elaborazione di un singolo carattere alla volta). Si noti inoltre che richiede gawk per il (valore del separatore record corrente) built-in RT variabile:

$ cat j.awk 
BEGIN { RS="[[:print:]]" } 
RT == "{" { bal++} 
RT == "}" { bal-- } 
{ printf "%s", RT } 
RT == "," && bal == 2 { print "" } 
END { print "" } 

$ gawk -f j.awk j.txt 
{"RomanCharacters":{"Alphabet":[{"RecordId":"1",...]}, 
{"RecordId":"2",...}, 
{"RecordId":"3",...}, 
{"RecordId":"4",...}, 
{"RecordId":"5",...} }} 
4

Dal momento che hai codificato tua domanda con sed, awk e Perl, ho capito che quello che si ha realmente bisogno è una raccomandazione per uno strumento. Mentre questo è un po 'fuori tema, credo che lo jq sia qualcosa che potresti usare per questo. Sarà meglio di sed o awk perché in realtà comprende JSON. Tutto ciò che viene mostrato qui con jq potrebbe anche essere fatto in Perl con un po 'di programmazione.

Supponendo che il contenuto nel modo seguente (in base al campione):

{"RomanCharacters":{"Alphabet": [ {"RecordId":"1","data":"data"},{"RecordId":"2","data":"data"},{"RecordId":"3","data":"data"},{"RecordId":"4","data":"data"},{"RecordId":"5","data":"data"} ] }} 

Si può facilmente riformattare questo a "prettify" è:

$ jq '.' < data.json 
{ 
    "RomanCharacters": { 
    "Alphabet": [ 
     { 
     "RecordId": "1", 
     "data": "data" 
     }, 
     { 
     "RecordId": "2", 
     "data": "data" 
     }, 
     { 
     "RecordId": "3", 
     "data": "data" 
     }, 
     { 
     "RecordId": "4", 
     "data": "data" 
     }, 
     { 
     "RecordId": "5", 
     "data": "data" 
     } 
    ] 
    } 
} 

E noi può scavare per i dati a recupera solo i record che ti interessano (indipendentemente dal loro contenuto):

$ jq '.[][][]' < data.json 
{ 
    "RecordId": "1", 
    "data": "data" 
} 
{ 
    "RecordId": "2", 
    "data": "data" 
} 
{ 
    "RecordId": "3", 
    "data": "data" 
} 
{ 
    "RecordId": "4", 
    "data": "data" 
} 
{ 
    "RecordId": "5", 
    "data": "data" 
} 

Questo è molto più leggibile, sia dagli umani che da strumenti come awk che processano il contenuto riga per riga. Se si desidera unire le linee per la lavorazione secondo la vostra domanda, l'awk diventa molto più semplice:

$ jq '.[][][]' < data.json | awk '{printf("%s ",$0)} /}/{printf("\n")}' 
{ "RecordId": "1", "data": "data" } 
{ "RecordId": "2", "data": "data" } 
{ "RecordId": "3", "data": "data" } 
{ "RecordId": "4", "data": "data" } 
{ "RecordId": "5", "data": "data" } 

Oppure, come @peak suggerito nei commenti, eliminare la parte awk di thie interamente utilizzando -c (uscita compatto JQ) opzione:

$ jq -c '.[][][]' < data.json 
{"RecordId":"1","data":"data"} 
{"RecordId":"2","data":"data"} 
{"RecordId":"3","data":"data"} 
{"RecordId":"4","data":"data"} 
{"RecordId":"5","data":"data"} 
+0

Nota che l'opzione -q di jq può essere sfruttata qui. Nell'esempio usando data.json, 'jq -c '. [] [] []' peak

+0

@peak - brillante, grazie per questo, per qualche motivo che non mi è mai venuto in mente. :-) L'ho aggiunto alla mia risposta. – ghoti

0

Utilizzando i dati di esempio forniti qui (quello che inizia con {Conti: {cliente ...), la soluzione a questo problema è quella che si legge nel file e mentre sta leggendo sta contando il numero di delimitatori definiti in $ /. Per ogni conteggio di 10.000 delimitatori, scriverà su un nuovo file. E per ogni delimitatore trovato, gli dà una nuova linea. Ecco come lo script appare come:

#!/usr/bin/perl 

$base="/home/dat789/incoming"; 
#$_="sample.dat"; 

$/= "}]},"; # delimiter to find and insert new line after 
$n = 0; 
$match=""; 
$filecount=0; 
$recsPerFile=10000; # set number of records in a file 

print "Processing " . $_ ."\n"; 

while (<>){ 
    if ($n < $recsPerFile) { 
     $match=$match.$_."\n"; 
     $n++; 
     print "."; #This is so that we'd know it has done something 
    }  
    else { 
     my $newfile="partfile".$recsPerFile."-".$filecount . ".dat"; 
     open (OUTPUT,'>', $newfile); 
     print OUTPUT $match; 
     $match=""; 
     $filecount++; 
     $n=0; 
    print "Wrote file " . $newfile . "\n"; 
    } 
} 

print "Finished\n\n"; 

Ho usato questo script contro il grande file 2,8 GB in cui il suo contenuto è formattato JSON one-liner. I file di output risultanti mancherebbero le intestazioni ei piè di pagina JSON corretti, ma ciò può essere facilmente risolto.

Grazie mille ragazzi per aver contribuito!