Traiter les fichiers CSV sous Gawk
Awk et Gawk sont des outils très puissants pour analyser des fichiers texte. Cependant, les fichiers CSV pose un problème particulier. Les champs sont séparés par des virgules ou des points virgules, selon les pays, mais on peut également retrouver ces séparateurs dans un champ entre guillemets doubles. Donc, impossible de découper les champs en se basant uniquement sur les séparateurs.
De plus, on peut retrouver des sauts de lignes dans un champ entre guillemets doubles, donc un champ peut occuper plusieurs lignes… Ce qui complique forcément les choses, Awk traitant les fichiers lignes par ligne.
L’extension gawk-csv
La solution la plus prometteuse pour traiter les fichiers CSV est sans doute l’extension gawk-csv de gawkextlib. Elle permet de traiter des fichiers CSV et fournit des fonctions permettant de lire et d’écrire des enregistrements CSV.
Avec cette extension, lire un fichier CSV se fait en 2 lignes :
@include "csv"
BEGIN { CSVMODE = 1 }
#... reste du programme ...
Traiter un fichier CSV en Gawk natif
Depuis la version 4.0, Gawk permet de traiter plus facilement les fichiers CSV grâce à la variable FPAT, qui permet de découper les champs d’entrée en fonction de leur contenu, au lieu de les découper en fonction de séparateurs.
FPAT
permet de correctement découper les lignes, mais il ne gère pas
le cas des champs contenant des passages à la ligne.
La mini-bibliothèque csv2awk
ci-dessous a été conçue pour traiter les fichiers CSV contenant des fins
de ligne. Pour cela, elle offre 2 fonctions, decoupe_csv()
et
produit_csv()
.
Elle présente les avantages suivants :
- Fonctionne sans dépendre d’une extension.
- Gère les passages à la ligne.
- Gère les fichiers CSV produits par LibreOffice et Excel.
- Permet de modifier un fichier CSV avec un programme Gawk ordinaire.
Elle a néanmoins quelques limitations :
- Si un champ contient des passages à la ligne, ce champ doit être entre guillemets doubles (ce qui est seulement recommandé par le RFC 4180).
- En l’état, elle ne traite que les fichiers en entrée de Gawk (i.e. pas les fichiers nommément ouverts, les commandes et coprocessus).
- Elle nécessite Gawk 4.0 ou plus.
- Après découpage, la ligne est séparée par des caractères
NULL
(caractère de code 0) etFS
est redéfini à cette valeur. Cela permet d’utiliser des fonctions entraînant une réévaluation de la ligne (sub
,gsub
,$0 = ...
) sans entraîner un redécoupage incorrect des champs. - En corolaire, si la ligne avant découpage contient un caractère
NULL
, en cas de réévaluation, les champs découpés ne correspondront plus aux champs lus dans le fichier CSV.
decoupe_csv()
La fonction decoupe_csv()
permet de traiter les fichiers CSV en Gawk
natif. Elle prend en paramètre le séparateur CSV utilisé (point-virgule
par défaut).
Elle découpe les champs de la ligne courante ($0
), si nécessaire lit
des lignes supplémentaires avec getline
et présente les champs comme
une ligne nativement découpée par Gawk (NF
, $0
, $1
, $2
, et
cætera).
Elle modifie le séparateur de champs (FS
), en le positionnant à NULL
(ASCII 0).
produit_csv()
La fonctionproduit_csv()
renvoie la ligne courante traduite au format
CSV. Elle prend en paramètre le séparateur CSV utilisé (point-virgule
par défaut).
Elle peut être utilisée après decoupe_csv()
pour afficher la ligne au
format CSV. Ce qui va permettre de modifier un fichier CSV avec les
fonctions Gawk classique sans avoir à tenir compte de son format.
Mais elle peut également être utilisée dans un script Gawk quelconque pour afficher la ligne en cours au format CSV.
La mini-bibliothèque awk2csv.awk
# csv2awk.awk - Traitement des fichiers CSV avec Gawk 4.0+
#
# Écrit en 2020 par Jean-Philippe Guérard <jean-philippe.guerard@tigreraye.org>
#
# Autant que légalement possible, l'auteur a placé tous les droits
# d'auteur et droits voisins de ce logiciel dans le domaine public,
# sans restriction géographique. Ce logiciel est distribué sans aucune
# garantie.
#
# Vous pouvez consulter la licence sur <http://creativecommons.org/publicdomain/zero/1.0/>.
#
# Utilisation :
#
# @include "csv2awk.awk"
# { decoupe_csv( ";" ) }
# ... traitement ...
# { print produit_csv( ";" ) }
#
function decoupe_csv( SEPARATEUR_CSV, MOTIF_CHAMP, NB, LIGNE_DECOUPEE, CHAMP_EST_COMPLET, I, J, LIGNE_CSV ){
if ( SEPARATEUR_CSV == "" ) { SEPARATEUR_CSV = ";" }
MOTIF_CHAMP = "([^" SEPARATEUR_CSV "]*)|(\"([^\"]|\"\")+(\"|$))"
# Lecture de la ligne CSV dans LIGNE_CSV
I = 1
split( "", LIGNE_CSV )
while( 1 ) {
sub( /\r$/, "" )
NB = patsplit( $0, LIGNE_DECOUPEE, MOTIF_CHAMP )
for ( J = 1 ; J <= NB ; J++ ){
CHAMP_EST_COMPLET = 0
if ( I in LIGNE_CSV ) {
LIGNE_CSV[ I ] = LIGNE_CSV[ I ] SEPARATEUR_CSV LIGNE_DECOUPEE[ J ]
} else {
LIGNE_CSV[ I ] = LIGNE_DECOUPEE[ J ]
}
if ( LIGNE_CSV[ I ] ~ /^"([^"]|"")+"$/ ){
LIGNE_CSV[ I ] = substr( LIGNE_CSV[ I ], 2, length( LIGNE_CSV[ I ] ) - 2 )
CHAMP_EST_COMPLET = 1
I++
} else if ( LIGNE_CSV[ I ] ~ "^[^" SEPARATEUR_CSV "\"]*$" ) {
CHAMP_EST_COMPLET = 1
I++
}
}
if ( CHAMP_EST_COMPLET ) break
if ( getline <= 0 ) break
$0 = LIGNE_CSV[ I ] "\n" $0
delete LIGNE_CSV[ I ]
}
# Reconstruction de la ligne d'entrée séparée par des caractères NULL
NF = 0
for ( I = 1 ; I <= length( LIGNE_CSV ) ; I++ ){
$I = gensub( /""/, "\"", "g", LIGNE_CSV[ I ] )
}
}
function produit_csv( SEPARATEUR_CSV, LIGNE_CSV, CHAMP_CSV, MOTIF_ECHAP ){
if ( SEPARATEUR_CSV == "" ) { SEPARATEUR_CSV = ";" }
MOTIF_ECHAP = "[\n\"" SEPARATEUR_CSV "]"
for ( I = 1 ; I <= NF ; I++ ){
if ( $I ~ MOTIF_ECHAP ){
CHAMP_CSV = "\"" gensub( /"/, "\"\"", "g", $I ) "\""
} else {
CHAMP_CSV = $I
}
if ( LIGNE_CSV ){
LIGNE_CSV = LIGNE_CSV SEPARATEUR_CSV CHAMP_CSV
} else {
LIGNE_CSV = CHAMP_CSV
}
}
return LIGNE_CSV
}
Essais
Essai 1 : affichage des champs d’un fichier CSV
Faisons un petit essai avec un fichier CSV créé à partir de la table suivante :
-------------------------------------------
| a | b | c | x y | "x " y" |
-------------------------------------------
Le fichier CSV contient :
a;b;c;"x y";"""x "" y"""
On sauvegarde le script sous le nom csv2awk.awk
, puis on ajoute un peu
de code pour afficher le contenu du fichier CSV :
echo 'a;b;c;"x y";"""x "" y"""' \
| gawk '@include "csv2awk.awk"
{ decoupe_csv() ; for (I=1 ; I<=NF ; I++) { print I ": " $I } }'
Résultat :
1: a
2: b
3: c
4: x y
5: "x " y"
Essai 2 : modification d’un fichier CSV
Nouvel essai, cette fois-ci de modification d’un fichier CSV. Nous partons du même fichier CSV que dans l’exemple précédent :
a;b;c;"x y";"""x "" y"""
Nous allons remplacer tous les guillemets doubles par des soulignés :
echo 'a;b;c;"x y";"""x "" y"""' \
| gawk '@include "csv2awk.awk"
{ decoupe_csv() ; gsub( /"/, "_" ) ; print produit_csv() }'
Résultat :
a;b;c;x y;_x _ y_