Newsgroup: it.comp.os.linux.iniziare
Titolo: script shell parsing file csv

ciao ho un file csv del tipo:
CAMPO1;CAMPO2;CAMPO3;CAMPO4;
volevo chiedere come posso fare in modo rapido da shell a sostituire
tutti i valori del campo3 (vuoti)con la stringa 'TEST'?

 man awk per maggiori info
Davide, perche' awk e non sed?
forse perche' il file ha struttura fissa e non dobbiamo fare una 
ricerca/sostituzione, ma sapendo gia' dove modificare andiamo di 
riscrittura diretta?
-- 
Una donna si veste bene per fare shopping, dare acqua alle piante, 
buttare la spazzatura, rispondere al telefono e prendere la posta.
Un uomo si veste bene per i matrimoni e per i funerali.


Essendo molti campi, tipo 30 forse converrebbe una scrittura puntuale,
come fareste con sed?

 puntuale, come fareste con sed?
no no, infatti era mera curiosita', poi rileggendo il post originario 
ho visto che si devono sostituire dei campi vuoti, quindi si rischia di 
sostituire in posti sbagliati.


 su 'campi'.
ok, grazie, do uno sguardo piu' approfondito alla man page.



mi chiedevo se c'era un modo con awk per sostituire direttamente i
valori di una colonna senza scrivere i print per tutte le colonne
rimanenti...

 rimanenti...
awk -F ';' -v OFS=";" '{$3="TEST";print}' tuofile


ottimo, grazie

 sostituire in posti sbagliati.
E' vero che awk è fatto apposta per i file strutturati per campi, però se la
domanda fosse stata (e avrebbe, in subordine, potuto anche
esserlo): "voglio sostituire con la stringa "TEST" solo i terzi campi
vuoti"?
awk -F ';' '{print $1";"$2";TEST;"$4}' prova.csv
sostituisce SEMPRE il terzo campo del file prova.csv con la stringa "TEST"
sed 's/;;/;TEST;/g' prova.csv
sostituisce TUTTI i campi vuoti del file prova.csv con la stringa "TEST", ma
può essere controproducente se (come in ipotesi) ***non*** voglio che siano
riempiti ***tutti*** i campi vuoti ma ***solo il terzo***; può essere però
una valida alternativa ad awk (vedi sotto) ***se io so già che ad essere
vuoto può essere solo il terzo campo***.
Se, come in ipotesi, l'esigenza fosse stata di sostituire con la
stringa "TEST" solo il terzo campo e solo quando questo fosse vuoto,
sarebbe molto più efficiente usare awk, ma penso che bisognerebbe farlo con
uno script che contenesse una struttura di controllo; o no?
RIASSUMENDO:
- voglio sostituire con TEST tutti i terzi campi (a prescindere dal fatto
che siano vuoti o no): awk è molto meglio di sed
- voglio sostituire con "TEST" tutti i campi vuoti: sed è molto meglio di
awk
- voglio sostituire con "TEST" tutti i terzi campi solo quando questi sono
vuoti: awk è molto meglio di sed, ma credo che non sia possibile farlo
senza una struttura di controllo (se il terzo campo contiene '.+' --regular
expression per "***almeno un*** carattere qualsiasi"-- allora '{print
$1";"$2";"$3";"$4}', altrimenti '{print $1";"$2";TEST;"$4}').
Che ne dite?

 '{print $1";"$2";"$3";"$4}', altrimenti '{print $1";"$2";TEST;"$4}').
Vedi sopra.
Ciao!

 (e non c'e' nulla di male, ovviamente)
Hai ragione: la verità è che sono due linguaggi completi (cioè dotati di
funzioni che coprono un'infinità di situazioni) e dunque, come hai
dimostrato con gli esempi di soluzione che hai dato, la differenza
sostanziale --almeno per il problema che stiamo discutendo-- può essere
soltanto quella della velocità a fronte di una grossa mole di lavoro da
svolgere.
Awk non lo conosco, non ho tempo (ora) per approfondirlo, ma ho provato le
tue due soluzioni e ho visto che funzionano, per cui me le terrò da parte:
non si sa mai.
Per quanto riguarda sed
-----------------------
Non ho dimestichezza con gli hold buffers in sed (ovviamente so che
esistono, ma ho sempre usato sed per operazioni piuttosto semplici), per
cui la tua risposta mi ha spinto a ridarci un'occhiata. Mi pare importante,
però, anche la regular expression utilizzata. Mi spiego meglio riguardo a
questo secondo punto. Seguimi, per favore, e correggimi se sbaglio.
Quando sed prova a confrontare le espressioni regolari su una linea, ricerca
le corrispondenze più lunghe su quella linea.
Quindi questo comando
sed 's/.*;/stringa_in_sostituzione/g'
produce effetti indesiderati perché usa come pattern l'occorrenza più lunga
(contenente quindi l'ultima più tutte le altre che la precedono: nel nostro
caso, comprendente l'ultimo campo delimitato da un ";" più tutti gli altri
che lo precedono), mentre quest'altro comando
sed 's/[^;]*;/stringa_in_sostituzione/g'
ha l'effetto desiderato, in quanto  mette nel pattern solo il primo campo
(tutto ciò che preceda il ";" e che sia diverso dal ";" e poi procede con
gli altri campi
Dunque, anziché inserire un'espressione regolare che indica "un numero
arbitrario di di caratteri terminante con il carattere ";", basta usare
un'espressione regolare che ricerchi un numero indefinito di caratteri che
***non*** siano ";", conclusa da un carattere ";". In questo modo si
ottiene il risultato di riconoscere la ricorrenza più breve
dell'espressione, anziché la più estesa.
Con questa tecnica di riconoscimento dell'occorrenza più breve e con l'uso
di un hold buffer (che contiene i primi due campi) si ottiene la soluzione
del nostro problema di sostituzione nel terzo campo.
La tua utile precisazione
«assumendo che le righe abbiano piu' di 3 campi (oppure 3, ma anche l'ultimo
terminato da ";").»
mi pareva potesse essere evitata con la seguente formulazione:
sed 's/^\([^;]*;[^;]*\);[;$]/\1;TEST;/' prova.csv
Ma poi mi è venuto in mente che ***nel pattern sed non inserisce i fine
riga***.
===> Come fare allora per coprire i casi in cui si debba sostituire il terzo
campo solo quando questo sia vuoto, ma in un file in cui è l'ultimo E non è
terminato da un ";"?
(
come nella seconda e quarta riga di questo:
1;2;3
4;5;
6;7;8
9;10;
)
===> Non mi torna solo il motivo per cui hai usato "^" all'inizio della
regular expression che serve per individuare il pattern. Che bisogno c'era
di fare riferimento a inizio riga?
sed 's/^\([^;]*;[^;]*\);;/\1;TEST;/' prova.csv
       ^
       ^
       ^
     questo
GRAZIE.

      questo
Non era strettamente necessario (*in questo specifico caso*), pero' imho e'
sempre meglio essere espliciti, sia per far capire meglio cosa vogliamo
fare, sia per "aiutare" il programma (o meglio, il "motore" che fa pattern
matching con le espressioni regolari) ad ottimizzare il lavoro.
Ciao!

 quindi inseriamo il nuovo valore.
OK, però nel caso in cui il campo possa terminare con fine riga, ma anche
con ";" (caso detto sopra del file "non normalizzato") devo ripetere due
volte l'operazione: prima una passata considerando "$" e poi un'altra
considerando ";" (vedi sopra), o viceversa.
Nel nostro caso:
sed 's/^\([^;]*;[^;]*\);$/\1;TEST;/' prova.csv > prova_temp.csv
                        ^
e poi
sed 's/^\([^;]*;[^;]*\);;/\1;TEST;/' prova_temp.csv > prova_mod.csv
                        ^
Due operazioni che possono essere unite anche...
1) con un pipe
o
2) con due editing (-e) in sequenza.
MA SOPRATTUTTO: due operazioni che POSSONO ESSERE rimpiazzate da...
3) sed 's/^\([^;]*;[^;]*\);\(;\|$\)/\1;TEST;/' prova.csv
o da
4) sed -r 's/^([^;]*;[^;]*);(;|$)/\1;TEST;/' prova.csv )
-------
P.S.
Sei al corrente di questo debugger?
http://sed.sourceforge.net/grabbag/scripts/sd.sh.txt
Immagino di sì.
Per chi, invece, fosse interessato a una serie di script pronti all'uso:
http://sed.sourceforge.net/grabbag/scripts/

 Immagino di sì.
Si', ma quando ne ho bisogno preferisco usare questo:
http://sedsed.sourceforge.net/
Ciao!

ATTENZIONE:
il mio software (KNode), nelle righe che devono essere mandate a capo
(opzione word-wrap in editing) e terminano con ";" elimina il ";"
Quindi, il comando:
sed 's/^\([^;]*;[^;]*\);;/\1;TEST;/' prova.csv | sed 's/^\([^;]*;[^;]*\)
$/\1;TEST;/'
è da leggersi come
sed 's/^\([^;]*;[^;]*\);;/\1;TEST;/' prova.csv | sed 's/^\([^;]*;[^;]*\);$/\1;TEST;/'
                                                                        ^
                                                                        ^
                                                                ripristinato
Ora ho tolto il word-wrap e il comando dovrebbe contenere il ";" prima del dollaro.