Ricette Miller¶
Concatenare in "verticale" più file¶
Il verbo "tipico" per concatenare due o più file è cat
. Ad esempio se voglio unire in verticale questi due file CSV
il comando da lanciare sarà
che darà in output
+--------+-------------+---------+------+
| nome | dataNascita | altezza | peso |
+--------+-------------+---------+------+
| andy | 1973-05-08 | 176 | 86.5 |
| chiara | 1993-12-13 | 162 | 58.3 |
| guido | 2001-01-22 | 196 | 90.4 |
| marco | 1983-12-08 | 183 | - |
| licia | 1993-12-07 | 158 | 57.9 |
+--------+-------------+---------+------+
È possibile fare il merge, l'unione in verticale, anche di due file con uno schema in parte diverso, perché Miller gestiste l'eterogeneità dei record. Se ad esempio si è in presenza di un file che ha una colonna in più (coloreOcchi
) rispetto a base.csv
, come questo (base_altro.csv
)
nome,dataNascita,altezza,peso,coloreOcchi
marco,1983-12-08,183,,verdi
licia,1993-12-07,158,57.9,neri
il verbo da usare è unsparsify
. Il comando sarà:
In output, verrà aggiunta la colonna coloreOcchi
, che non sarà valorizzata per i record del file che in partenza non aveva questa colonna:
+--------+-------------+---------+------+-------------+
| nome | dataNascita | altezza | peso | coloreOcchi |
+--------+-------------+---------+------+-------------+
| andy | 1973-05-08 | 176 | 86.5 | - |
| chiara | 1993-12-13 | 162 | 58.3 | - |
| guido | 2001-01-22 | 196 | 90.4 | - |
| marco | 1983-12-08 | 183 | - | verdi |
| licia | 1993-12-07 | 158 | 57.9 | neri |
+--------+-------------+---------+------+-------------+
Suddividere un file di input in più file di output, ogni xxx record¶
mlr --csv put -q '
begin {
@batch_size = 1000;
}
index = int(floor((NR-1) / @batch_size));
label = fmtnum(index,"%04d");
filename = "part-".label.".json";
tee > filename, $*
' ./input.csv
Verrà creato un file di output, con nome part-000XXX
, ogni 1000 (si imposta tramite @batch_size
) record.
Estrarre le righe che contengono il valore massimo di un campo¶
Alcune delle righe sottostanti, sono identiche, fatta eccezione per il V campo.
1,861265,C,A,0.071
1,861265,C,A,0.148
1,861265,C,G,0.001
1,861265,C,G,0.108
1,861265,C,T,0
1,861265,C,T,0.216
2,193456,G,A,0.006
2,193456,G,A,0.094
2,193456,G,C,0.011
2,193456,G,C,0.152
2,193456,G,T,0.003
2,193456,G,T,0.056
Se si vogliono estrarre soltanto quelle con il valore massimo del V campo, raggruppate per i valori degli altri 4, il verbo da usare è top
mlr --csv -N top -f 5 -g 1,2,3,4 input.tsv
Vedi https://stackoverflow.com/a/70664880/757714
Eseguire un comando esterno all'interno di una funzione¶
All'interno di un comando Miller è possibile lanciare una utility esterna, usando la funzione system
.
Immaginiamo ad esempio di avere un file come questo
e di voler applicare il cosiddetto natural sorting alla stringa 15,1,2/AX,22,1/C,1/A,1/BA,2,3
, ottenendo questo ordinamento 1,1/A,1/BA,1/C,2,2/AX,3,15,22
.
Utilizzando le utility standard della shell di Linux basterebbe fare così (in paste
si usa -
perché l'input è l'stdin
):
Per riportare questa sintassi1 in un comando Miller, il comando sarebbe come questo di sotto, in cui viene creato il campo toto
, che raccoglie valori derivanti dal lancio di utility esterne, grazie alla funzione system
.
<input.txt mlr --c2p --barred cat then put -S '$toto=system("echo ".$b." | tr , \"\n\" | sort -V | paste -sd, -")'
E l'output:
+---+-------------------------------+-------------------------------+
| a | b | toto |
+---+-------------------------------+-------------------------------+
| 1 | 15,1,2/AX,22,1/C,1/A,1/BA,2,3 | 1,1/A,1/BA,1/C,2,2/AX,3,15,22 |
+---+-------------------------------+-------------------------------+
Nel comando bisogna avere cura di inserire eventuali escape
a caratteri come "
.
Fare un trova e sostituisci per campo¶
È un'operazione classica che si realizza sfruttando il verbo put
e funzioni come sub
e gsub
.
Se ad esempio si vuole cercare la stringa Denis
e sostituirla con Dennis
, nel campo nomeCampo
, la sintassi tipo è:
La funzione sub
ha tre argomenti, separati ,
:
- a cosa applicare la sostituzione;
- cosa cercare;
- con cosa sostituirlo.
Alcune note:
- i nomi dei campi, nelle funzioni scritte in
put
hanno come prefisso il$
. Se ci sono spazi usare le parentesi graffe (i.e.${nome campo}
); - in questo esempio si dice al campo
nomeCampo
, che sarà uguale a se stesso, con le sostituzioni da fare.
È possibile usare le espressioni regolari, usando la sintassi del caso, ed è possibile mettere in fila enne processi di sostituzione, separati da ;
:
Nota bene
sub
esegue il trova e sostituisci della prima occorrenza che trova in una cella, mentre gsub
per tutte le occorrenze in quella cella.
Fare un trova e sostituisci globale¶
È comodo utilizzare DSL
, il linguaggio di scripting di Miller e usare un ciclo for:
Per tutti i campi - k
- verrà applicata la funzione gsub
(trova e sostituisci globale con supporto a espressioni regolari), che (in questo esempio) cerca la stringa e
e la sostituisce con X
.
L'opzione -S
per forzare che tutti i campi siano interpretati come stringhe.
Se usi Miller 5
Il comando cambia in questo modo, inserendo l'opzione -S
dopo il verbo put
:
Fare un trova e sostituisci per campo - nuovi verbi¶
La release 6.9
di Miller ha introdotto i nuovi verbi sub
, gsub
e ssub
, per applicare comandi di trova e sostituisci, in modo molto più diretto e comodo della modalità descritta sopra.
Con i verbi sub
e gsub
sono supportate le espressioni regolari.
Se ad esempio ho questa tabella di input:
nome | dataNascita | altezza | peso |
---|---|---|---|
andy | 1973-05-08 | 176 | 86.5 |
chiara | 1993-12-13 | 162 | 58.3 |
guido | 2001-01-22 | 196 | 90.4 |
E voglio sostituire 1973
con 2021
nella colonna dataNascita
, si può lanciare questo comando:
Ed ottenere:
nome | dataNascita | altezza | peso |
---|---|---|---|
andy | 2021-05-08 | 176 | 86.5 |
chiara | 1993-12-13 | 162 | 58.3 |
guido | 2001-01-22 | 196 | 90.4 |
Rimuovere i ritorni a capo nelle celle¶
Prendendo spunto dalla ricetta sul trova e sostituisce globale, basta cercare il carattere di ritorno a capo.
In input un CSV come quello di sotto (qui), in cui all'interno delle celle, ci sono dei ritorno a capo materializzati da dei line feed, ovvero mappati con i caratteri speciali \n
.
Campo 1,Campo 2
"Cella con
A capo
Fastidiosi",Ipsum
Lorem,"uno
Due
Tre
Quattro
Cinque"
Si può cercare appunto \n
e sostituirlo con spazio, e poi rimuovere eventuali doppi spazi usando il verbo clean-whitespace
:
mlr --csv -S put 'for (k in $*) {$[k] = gsub($[k], "\n", " ")}' then clean-whitespace rimuovi-a-capo.txt
In output:
Nota bene
\n
non è l'unico modo di materializzare un ritorno a capo, quindi è possibile dover modificare l'esempio di sopra.
Suddividere un file in più parti, in dipendenza del valore di un campo¶
Il verbo da usare è tee
.
A partire ad esempio da
DeviceID,AreaName,Longitude,Latitude
12311,Dubai,55.55431,25.45631
12311,Dubai,55.55432,25.45634
12311,Dubai,55.55433,25.45637
12311,Dubai,55.55431,25.45621
12309,Dubai,55.55427,25.45627
12309,Dubai,55.55436,25.45655
12412,Dubai,55.55441,25.45657
12412,Dubai,55.55442,25.45656
e lanciando
mlr --csv --from input.csv put -q 'tee > $DeviceID.".csv", $*'
si avranno questi tre file
#12311.csv
DeviceID,AreaName,Longitude,Latitude
12311,Dubai,55.55431,25.45631
12311,Dubai,55.55432,25.45634
12311,Dubai,55.55433,25.45637
12311,Dubai,55.55431,25.45621
#12412.csv
DeviceID,AreaName,Longitude,Latitude
12412,Dubai,55.55441,25.45657
12412,Dubai,55.55442,25.45656
#12309.csv
DeviceID,AreaName,Longitude,Latitude
12309,Dubai,55.55427,25.45627
12309,Dubai,55.55436,25.45655
Aggiungere una colonna con il conteggio dei valori distinti di un (o più) campo¶
A partire da
nome | dataNascita | altezza | peso | comuneNascita | sesso |
---|---|---|---|---|---|
andy | 1973-05-08 | 176 | 86.5 | Roma | maschio |
chiara | 1993-12-13 | 162 | 58.3 | Milano | femmina |
guido | 2001-01-22 | 196 | 90.4 | Roma | maschio |
sara | 2000-02-22 | 166 | 70.4 | Roma | femmina |
giulia | 1997-08-13 | 169 | 68.3 | Milano | femmina |
si vuole aggiungere un colonna che riporti il numero di valori distinti del campo comuneNascita
.
Si può usare count-similar
. Il comando è
che in output restituisce
nome | dataNascita | altezza | peso | comuneNascita | sesso | count |
---|---|---|---|---|---|---|
andy | 1973-05-08 | 176 | 86.5 | Roma | maschio | 3 |
guido | 2001-01-22 | 196 | 90.4 | Roma | maschio | 3 |
sara | 2000-02-22 | 166 | 70.4 | Roma | femmina | 3 |
chiara | 1993-12-13 | 162 | 58.3 | Milano | femmina | 2 |
giulia | 1997-08-13 | 169 | 68.3 | Milano | femmina | 2 |
Due note:
- si può cambiare nome alla colonna di output usando il parametro
-o
; - si possono conteggiare i valori distinti per combinazioni di più campi. Ad esempio
-g comuneNascita,sesso
.
Estrarre una colonna in modo numerico¶
In Miller si può puntare a una colonna in modo numerico, soltanto dentro codice DSL (il codice di programmazione di Miller).
Se si vuole ad esempio estrarre la colonna numero 3
, si può fare così:
mlrgo --csv put -q '
@i = 0;
for (k, v in $*) {
@i += 1;
if (@i == 3) {
emit {k: v};
break;
}
}
' input.csv
Nel codice di sopra la terza colonna, la numero 3
, si fissa con @i == 3
.
-
qui commentata su explainshell ↩