Come analizzare un file CSV in Bash?
Arrivando in ritardo a questa domanda e poiché bash offre nuove funzionalità, perché questa domanda riguarda bash e poiché nessuna delle risposte già pubblicate mostra questo modo potente e conforme di fare precisamente questo .
Analisi dei file CSV in bash
, utilizzando il modulo caricabile
Conforme a RFC 4180 , una stringa come questa riga CSV di esempio :
12,22.45,"Hello, ""man"".","A, b.",42
dovrebbe essere diviso come
1 12
2 22.45
3 Hello, "man".
4 A, b.
5 42
bash caricabile .C moduli compilati.
Sotto bash, puoi creare, modificare e utilizzare moduli compilati in c caricabili . Una volta caricati, funzionano come qualsiasi altro integrato !! (Puoi trovare maggiori informazioni nell'albero dei sorgenti.;)
L'albero dei sorgenti corrente (15 ottobre 2021, bash V5.1-rc3) contiene una serie di esempi:
accept listen for and accept a remote network connection on a given port
asort Sort arrays in-place
basename Return non-directory portion of pathname.
cat cat(1) replacement with no options - the way cat was intended.
csv process one line of csv data and populate an indexed array.
dirname Return directory portion of pathname.
fdflags Change the flag associated with one of bash's open file descriptors.
finfo Print file info.
head Copy first part of files.
hello Obligatory "Hello World" / sample loadable.
...
tee Duplicate standard input.
template Example template for loadable builtin.
truefalse True and false builtins.
tty Return terminal name.
uname Print system information.
unlink Remove a directory entry.
whoami Print out username of current user.
C'è un cvs
completamente funzionante parser pronto per l'uso in examples/loadables
directory:csv.c !!
In un sistema basato su Debian GNU/Linux, potrebbe essere necessario installare il pacchetto bash-builtins tramite
apt install bash-builtins
Utilizzo di integrati bash caricabili :
Quindi:
enable -f /usr/lib/bash/csv csv
Da lì, puoi usare csv
come integrato bash .
Con il mio campione:12,22.45,"Hello, ""man"".","A, b.",42
csv -a myArray '12,22.45,"Hello, ""man"".","A, b.",42'
printf "%s\n" "${myArray[@]}" | cat -n
1 12
2 22.45
3 Hello, "man".
4 A, b.
5 42
Quindi in un ciclo, elaborando un file.
while IFS= read -r line;do
csv -a aVar "$line"
printf "First two columns are: [ '%s' - '%s' ]\n" "${aVar[0]}" "${aVar[1]}"
done <myfile.csv
Questo modo è chiaramente il più veloce e il più efficace rispetto all'utilizzo di qualsiasi altra combinazione di builtin bash o fork di qualsiasi binario.
Sfortunatamente, a seconda dell'implementazione del tuo sistema, se la tua versione di bash è stata compilata senza loadable
, potrebbe non funzionare...
Esempio completo con campi CSV multilinea.
Ecco un piccolo file di esempio con 1 titolo, 4 colonne e 3 righe. Perché due campi contengono newline , i file sono 6 lunghezza delle righe.
Id,Name,Desc,Value
1234,Cpt1023,"Energy counter",34213
2343,Sns2123,"Temperatur sensor
to trigg for alarm",48.4
42,Eye1412,"Solar sensor ""Day /
Night""",12199.21
E un piccolo script in grado di analizzare correttamente questo file:
#!/bin/bash
enable -f /usr/lib/bash/csv csv
file="sample.csv"
exec {FD}<"$file"
read -ru $FD line
csv -a headline "$line"
printf -v fieldfmt '%-8s: "%%q"\\n' "${headline[@]}"
while read -ru $FD line;do
while csv -a row "$line" ; ((${#row[@]}<${#headline[@]})) ;do
read -ru $FD sline || break
line+=$'\n'"$sline"
done
printf "$fieldfmt\\n" "${row[@]}"
done
Questo può rendere:(ho usato printf "%q"
per rappresentare caratteri non stampabili come newline come $'\n'
)
Id : "1234"
Name : "Cpt1023"
Desc : "Energy\ counter"
Value : "34213"
Id : "2343"
Name : "Sns2123"
Desc : "$'Temperatur sensor\nto trigg for alarm'"
Value : "48.4"
Id : "42"
Name : "Eye1412"
Desc : "$'Solar sensor "Day /\nNight"'"
Value : "12199.21"
Puoi trovare un esempio funzionante completo qui:csvsample.sh.txt orcsvsample.sh.
Avviso:
Ovviamente, l'analisi di CSV utilizzando questo non è perfetta! Questo funziona per molti semplici file CSV, ma attenzione alla codifica e alla sicurezza!! Ad esempio, questo modulo non sarà in grado di gestire i campi binari!
Leggi attentamente i commenti del codice sorgente csv.c e RFC 4180!
Possiamo analizzare i file csv con stringhe tra virgolette e delimitate da say | con il seguente codice
while read -r line
do
field1=$(echo "$line" | awk -F'|' '{printf "%s", $1}' | tr -d '"')
field2=$(echo "$line" | awk -F'|' '{printf "%s", $2}' | tr -d '"')
echo "$field1 $field2"
done < "$csvFile"
awk
analizza i campi stringa in variabili e tr
rimuove la citazione.
Leggermente più lento di awk
viene eseguito per ogni campo.
Dal man
pagina:
-d delimIl primo carattere di delim è usato per terminare la riga di input, invece del newline.
Stai usando -d,
che terminerà la riga di input sulla virgola. Non leggerà il resto della riga. Ecco perché $y è vuoto.
Devi usare IFS
invece di -d
:
while IFS=, read -r col1 col2
do
echo "I got:$col1|$col2"
done < myfile.csv
Si noti che per l'analisi CSV per scopi generici è necessario utilizzare uno strumento specializzato in grado di gestire i campi tra virgolette con virgole interne, tra gli altri problemi che Bash non è in grado di gestire da solo. Esempi di tali strumenti sono cvstool
e csvkit
.