Newsgroup: it.comp.www.php
Titolo: Parsing file di testo

Salve!
Ho per le mani un file di testo (di qualche decina di migliaia di righe,
diviso in sezioni) che devo elaborare e, riga per riga, inserire i dati
in un database Postgres.
Ora, per il parsing non c'è problema, la questione che mi porgo è
questa: al momento ho pensato ad una soluzione del tipo:
- carico il file sul server tramite uno script PHP;
- allo stesso script faccio chiamare un file in perl (
parser.pl
) che si
  lavora per benino il file e che inserisce i dati nel DB.
- alla fine dell'esecuzione del file in Perl lo script PHP avverte
  dell'avvenuta operazione ed elimina il file caricato.
Quello che mi chiedevo è: conviene passare per il Perl per il parsing
del file (che ha una certa logica, ma di fondo è un bel traffico!!)?
Ok che anche in PHP esistono le espressioni regolari compatibili con il
Perl, ma forse il Perl è più indicato a livello di pura velocità?
Giusto per completezza, il file in Perl fa una cosa del genere (per chi
ci capisce di Perl!):
open(FILE, "il_mio_file");
open (FILE2, "> new");
while(<FILE>) {
 s/\s{170}//;
 s/^\d+\s// unless /^43/;
 if(/^43/) {
  s/^43/^@/g;
  s/\n//;
  s/\s{2,}/\n/g;
  s/^^@\s+//;
 }
 s/\s{2,}\n/\n/;
 /^Http:\/\/ / and s/^Http:\/\/ /http:\/\//;
 print FILE2;
}
close FILE;
close FILE2;
rename("new", "il_mio_file");
In pratica: mi conviene riscrivere tutto il parsing utilizzando le varie
funzioni preg_* del PHP, in modo da evitare di dovermi appoggiare ad un
file "esterno" che fa il lavoro sporco oppure mi conviene chiamare
l'esecuzione di tale file dall'interno dello script PHP?
Che entrambi facciano bene il lavoro (se bene istruiti a farlo!) è fuori
di dubbio, ma a livello di velocità (forse l'elemento più importante) ci
sono differenze?
Accetto consigli! :-)
Ciao
--ev

 ha scritto nel messaggio news:2ku9p9...@127.0.0.1...


sono differenze?
Sulla lunga distanza Perl e' circa un 30-50% piu' veloce, e forse di piu',
per questioni di buffers ecc.... ma sconta una penalita' all'avvio. Il mio
Perl e' piuttosto arrugginito - non riesco a capire che formato ha il file
di testo originale - ma si potrebbe forse usare anche solo una "pipe" solo
con sed, tr e grep, e psql in fondo 8-D. Cosi' basta una popen().
Leonardo cui garba pipa'
-- 
Nior thainig ar major i dtus an lae chugainn,
is ni rabhamar fein ann i gcoir na gceart
ach mar sheolfai aoireacht bo gan aoire,
ar thaobh na greine de Sliabh na mBan.

 di esecuzione così sei sicuro!!!!
Non intendevo velocità in quel senso! Ciò che intendevo era più o meno:
il perl lavora molto velocemente con sostituzioni, matching ecc; se
faccio la stessa cosa in PHP rischio di ottenere l'effetto tartaruga?
Ok che la velocità dell'interprete intacca la velocità di esecuzione
dll'intero script, ma quella non è un problema (come giustamente hai
detto, si lavora di timeout).
Il fatto è che lo script *deve* parsare il file nel minor tempo
possibile per un serie di motivi che non sto qui ad elencare!
A 'sto punto faccio un confronto fra uno script perl ed uno in php e
vedo chi si comporta meglio!
Ciao
--ev

 con sed, tr e grep, e psql in fondo 8-D. Cosi' basta una popen().
Il file č uno schifo! :-)
Ridotto all'osso e semplificato č fatto da un codice di due cifre
seguito da un numero (credo random, ma non ho voglia di controllarmi
400.000 righe di roba!) di spazi e da del testo ti lunghezza variabile
(con in fondo una serie di spazi in modo che ogni riga contenga un
numero fisso di caratteri).
Le mie conoscenze in fatto della triade (sed, tr e grep ... e ci
aggiungiamo pure un cut??) sono abbastanza limitate, quindi l'idea
diventa:
- carico il file (sempre con il buon php, che mi deve fare anche altre
  cosucce)
- parso di perl, trasformando in SQL
- faccio un fischio a postgres che se piji 'l file e lo inglobi
- cancello il reperto
- vivo felice?
Qualcuno offre di piů?
Ciao
--ev che lo sapevo che Leo rispondeva!

Qualcuno offre di piů?
Io:
	$perl = popen("perl -- perlscript | psql -f mytable", "w");
	while(!feof($fp))
	{
		...
		fwrite($perl, $output);
	}
	fclose($perl);
	fclose($fp);
...cosi' le righe originali lette da $fp *non* finiscono in alcun file
intermedio. Risparmi disco e CPU, e non devi cancellare.
Leonardo


 intermedio. Risparmi disco e CPU, e non devi cancellare.
Chiarissimo: prendo l'output del mio script e lo invio mano a mano a
postgres ....
Ma a questo punto (si, lo so, ogni tanto li rompo parecchio!) non mi
conviene usare i driver del DB dallo script perl e, mano a mano che
parso una riga (o meglio, una sezione) la invio al DB? Anche cosě mi
sparirebbe il file intermedio! (in effetti scemo io che non ci avevo
pensato!) Caricherň di piů lo script Perl, ma alla fine il lavoro
qualcuno lo dovrŕ poi fare!
Rilanci?
Ciao
--ev 

sparirebbe il file intermedio!
Si' e no. Mmm. Bisognerebbe vedere cos'e' piu' veloce... *in genere*
queste cose le scrivo in C.


 queste cose le scrivo in C.
A 'sto punto mi metto a fare dei test di velocità, rapportando Perl, PHP
e quel tuo affare in sed (!!), con e senza file intermedio (che alla
fine mi resta sulla macchina per non più di 20 secondi, quindi non p un
problema!)
Grazie del preziosissimo aiuto! :-)
Ciao
--ev

e le barre verticali!
Le regexp sono regexp :-)


problema!)
Bene. Se posti tempi e dati macchina, si prova con un programmino in


 C.
Guarda, te lo escludo!
E ti spiego perchè: quel programma "parsa tutto" andrà bene così com'è
per 2/3 mesi non di più, poi il file con i dati sarà differente, quindi
dovrò riscrivere il parser ... E non me la sento di mandarti un'email
chiedendoti di farmi la patch! :-)
Comunque: provetta veloce veloce (senza sputare i dati nel DB, che tanto
la velocità dipende dal DB e non dal linguaggio [1]): il file è quello che
dicevo (un bel wc riporta: 39126, 120655, 8764224), la struttura
l'abbiamo capita ed un 
$ time 
parser.pl
 il_mio_file
riporta:
real  0m7.891s
user  0m7.050s
sys   0m0.230s
con lo script che si limita a scrivere el righe parsate in un secondo
file (non SQL compatibile) del tipo:
[prima]
1 Nome       $
2 Cognome    $
3  Indirizzo $
43   Anvedi c$
43 he il 43 c$
43 'è sempre?$
[Dopo]
Nome$
Cognome$
Indirizzo$
Anvedi che il 43 c'è sempre?$
La macchina è un "glorioso" P200 con i suoi 64 MB di RAM (e non ditelo
in giro che è la macchina di produzione!)
Adesso mi ripasso un po' il sed e metto a posto il tuo codice in modo
che faccia la stessa cosa del mio script così da fare un parallelo, e mi
installo pure il php come cgi per poter cronometrare anche le prove in
PHP!
Ciao
--ev
[1] E poi ho postgres che fa il matto! Perchè con
=> select date ('now');
mi si ostinerà a visualizzare 01-01-2000 e con time('now') invece
01:00:00? Mah ...





Uhm. Non so; ho provato con sed su un file del tipo
  40000  112000 8695981 test.txt
ci ha messo sei secondi per una semplice eliminazione di trailing spaces.
La versione in C, che poi sono novanta righe delle mie[1], opera:
[prima]
1 Nome       $
2 Cognome    $
3  Indirizzo $
43   Anvedi c$
43 he il 43 c$
43 'è sempre?$
[Dopo]
INSERT INTO table VALUES('Nome','Cognome','Indirizzo','Anvedi... sempre?');
e ottiene tempi alquanto migliori:
real    0m0.953s
user    0m0.810s
sys     0m0.150s
Leonardo
[1] io seguo le convenzioni "many true braces" e "spanned-printf", per cui
    per fare una "malloc" mi ci vogliono cinque linee 8-D. In obfuscated C
    e' solo una schermata:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC         43
void inline initCodes(char **s){size_t f;for(f=0;f<=MAXC;f++) s[f][0]=0; }
void inline output(char **v) {
    printf("INSERT INTO table VALUES('%s','%s','%s','%s');\n",
    v[1], v[2], v[3], v[MAXC]); initCodes(v); }
char inline *trim(char *b) { char *p, *q; for(p=b+1; *p!=' '; p++);
for(;*p==' ';p++); for(q=p+strlen(p);*q<=' ';q--); *(++q) = 0; return p; }
int main() {
    char **szValue; char buffer[    256    ]; size_t f, code; int t=0;
    szValue = calloc(MAXC+1, sizeof(char *));
    for (f=0; f <= MAXC; f++) szValue[f] = malloc(sizeof(buffer)-1);
    initCodes(szValue); while(!feof(stdin)) {
        *buffer = '\n'; fgets(buffer, sizeof(buffer), stdin);
        if ('\n' == *buffer) continue;
        code = atoi(buffer); if (   ((    43    )) == code) {
            strncat(szValue[code], trim(buffer), sizeof(buffer)-1); t=1;
        } else { if (t) { output(szValue); t = 0; }
            strncpy(szValue[code], trim(buffer), sizeof(buffer)-1); } }
    if (t) { output(szValue); t = 0; } return EXIT_SUCCESS; }


 sys     0m0.150s
A parte il fatto che non ho capito molto come utilizzare il tuo codice
(credevo bastasse un onesto 
$ tuo_programma file_con_i_dati
ma evidentemente non è così! :-)
E, ripeto, io di C ne capisco veramente pochissimo (niente?).
BTW, ho fatto delle prove anche con il php (da linea di comando, perchè
misurargli il tempo via browser era un po' disonesto!): per fare tutto
l'ambaradan di cui ho bisogno ci mette più del doppio dello script in
Perl ...
A 'sto punto mi sa che mi tengo il lento startup del perl che mi porta
poi a risultati tutto sommato buoni, considerando anche che lo script
andrà caricato su un bel macchinone, cosa che velocizzerà (anche se poi
non di moltissimo!) il parsing.
Leo, a questo punto grazie, anche se alla fine sono tornato nella
direzione dalla quale sono venuto! :-)
Ciao
--ev

$ tuo_programma file_con_i_dati
$ mio_programma < file_con_i_dati > file.sql
Leonardo che ni garba pipa'


 $ mio_programma < file_con_i_dati > file.sql
Avevo già provato con:
$ leo < file_dei_dati
aspettandomi una marea di dati in output.
Il problema è che il programma, al momento di leggersi il file, mi va in
segfault: ho provato (a naso!!) ad aumentare il MAXC, anche se in verità
non so cos'ho fatto, e comunque il segfault non c'è più!
E comunque, ci sarebbe un altro problema: come ti dicevo, il file
contiene a sinistra i codici numerici, a destra le descrizioni: solo che
io per esemplificare ti ho fatto una lista di 4 campi, ma il file ne
contiene molti di più; e il bello è che, ad esempio, in alcune sezioni
posso avere il codice "4", in altre no ma può saltarmi fuori un codice
"12"! Insomma, un casotto!
TI manderei il file ma purtroppo "dall'alto" lo proibiscono (sono dati
personali di aziende ed imprese, e come ovvio non posso mandarli a
destra e a sinistra!)
Comunque, ho deciso: mi arrangio con il perl nche se ci metto 3 secondi
in più!
Ciao e arigrazie! :-)
--ev

"12"! Insomma, un casotto!
Dov'e' il problema? Basta modificare due punti:
    printf("INSERT INTO table VALUES('%s','%s','%s','%s');\n",
    v[1], v[2], v[3], v[MAXC]); initCodes(v); }
Questo stampa solo quattro campi, e non va bene. Meglio:
// Questo funziona per qualsiasi numero di campi. I campi mancanti
// vengono inseriti come campi VUOTI, non come campi NULL.
void inline output(char **v)
{
	size_t t;
	printf("INSERT INTO table VALUES(";
	for (t=1; t < MAXC; t++)
	{
		printf("'%s', ", v[t]);
	}
	printf("'%s');\n", v[MAXC]);
	initCodes(v); 
}
e in quest'altro punto:
    char **szValue; char buffer[    256    ]; size_t f, code; int t=0;
                                ^^^^^^^^^^^
meglio se diventa buffer[1024]: e' il massimo numero di caratteri in ogni
riga. Altrimenti altro che segmentation fault! 8-D
Leonardo
