8
votes

Comment gérer les virgules dans un fichier CSV en cours de lecture par Bash Script

Je crée un script Bash pour générer une sortie à partir d'un fichier CSV (j'ai plus de 1000 entrées et je ne l'ai pas imaginée à la main ...).

Le contenu du fichier CSV semble semblable à Ceci: xxx

J'ai du code qui peut séparer les champs à l'aide de la virgule comme délimiteur, mais certaines valeurs contiennent des virgules, telles que adygeya, république . Ces valeurs sont entourées de citations pour indiquer que les caractères à l'intérieur doivent être traités dans le cadre du champ, mais je ne sais pas comment analyser cela pour en tenir compte.

actuellement, j'ai cette boucle: < / p> xxx

qui produit cette sortie pour les données d'échantillon indiquées ci-dessus: xxx

Comme vous pouvez le constater, la troisième entrée est analysée, la troisième entrée est analysée. incorrectement. Je veux qu'il produise xxx


9 commentaires

Voir Stackoverflow.com/Questtions/7804673/...


Merci @tomwrittock, je vais enquêter sur le lien donné par cette réponse, je n'ai jamais utilisé awk avant, il aura probablement besoin de swoter dessus (pour le bénéfice de tous les autres, le lien est: backreference.org/2010/04/17/csv-parsing-with-awk )


Vous ne pouvez pas simplement réexporter les données avec '|', onglet ou un autre caractère qui n'apparaît pas dans l'entrée? Bonne chance.


@shellter Malheureusement, je n'ai pas de contrôle sur l'exportation des données


Également la recherche sur Google Groupes for comp.lang.awk. Il y a eu une discussion de 3 mois sur le traitement des CSV il y a 10 ans. Quelques solutions très sophistiquées. Bonne chance.


Similaire


@Denniswilliamson La réponse acceptée sur cette question est essentiellement «N'utilisez pas AWK pour traiter des données de CSV difficiles», cela ne m'aidait pas à traiter mes données, bien que cela aide à éliminer les options.


Ce n'est pas la seule chose qu'il dit. Il dit également d'utiliser un module Python ou Perl qui est composé spécialement pour le traitement des fichiers CSV.


N'ayant jamais utilisé Python / Perl, j'étais chiante de ça, pas une grande raison, mais ma motivation pour cela piratait ensemble quelque chose pour me sauver manuellement édition de quelques milliers de lignes dans un format spécifique ...


6 Réponses :


2
votes

Après avoir pensé au problème, j'ai compris que, puisque la virgule de la chaîne n'est pas importante pour moi, il serait plus facile de le supprimer simplement de l'entrée avant d'analyser.

à cette fin, j'ai Concocalisé une commande sed code> correspondant à des chaînes entourées de citations doublées contenant une virgule. La commande supprime ensuite les bits que vous ne voulez pas de la chaîne correspondante. Il le fait en séparant la regex en sections mémorisées. P>

Cette solution ne fonctionne que lorsque la chaîne contient une seule virgule entre les guillemets doubles. P>

La regex non évaluée est p>

[Australian Capital Territory] [AU-ACT] [20034] [AU] [Australia]
[Piaui] [BR-PI] [20100] [BR] [Brazil]
[Adygeya Republic] [RU-AD] [21250] [RU] [Russian Federation]
[Bío-Bío] [CL-BI] [20154] [CL] [Chile]


2 commentaires

Il y a quelques cas spécifiques où cela peut fonctionner et beaucoup de cas où il ne sera pas. Un problème significatif est que dans sed correspond à des correspondances telles que . * sont gourmands.


Merci pour les commentaires. Avec mon contribution, je crois que cela fonctionnera bien, mais je suis intéressé à déterminer comment améliorer la solution générale. Serait-ce une amélioration? (") (^, *) (,) (^" *) (") apparemment sed ne prend pas en charge les correspondances paresseuses, mais une classe de caractères annulée peut fonctionner. Les devis échappés causeraient également des problèmes, j'attends



5
votes

Après avoir regardé SOLUTION STRAND> @ DIMITRE sur ici . Vous pouvez faire quelque chose comme ça - xxx

test: xxx

pour supprimer " vous pouvez tuyer la sortie à sed . xxx


5 commentaires

Merci, je ne sais pas pourquoi c'est wiki communautaire, mais je vais vérifier cela :)


@chrisbunney Depuis que j'ai pris la solution de Dimitire comme référence, je pensais qu'il serait inapproprié de prendre un crédit pour cette réponse. :)


Je viens de tester cela, il ne produit pas la même sortie pour moi que cela fait pour vous. En fait, il produit la même sortie "mauvaise" que j'ai décrite dans ma question


@chrisbunney ressemble à awk version de version. Je l'ai testé sur gnu-awk v 4.0.0


Oui, avec un peu d'aide de @dimitre, il s'avère une ancienne version de Awk sur ma machine



8
votes

Si vous voulez tout faire tout dans awk em> ( gnu awk 4 em> strud> est requis pour que ce script fonctionne comme prévu):

awk '{
 n = parse_csv($0, data)
 for (i = 0; ++i <= n;) {
    gsub(/,/, " ", data[i])
    printf "[%s]%s", data[i], (i < n ? OFS : RS)
    }
  }
function parse_csv(str, array,   field, i) { 
  split( "", array )
  str = str ","
  while ( match(str, /[ \t]*("[^"]*(""[^"]*)*"|[^,]*)[ \t]*,/) ) { 
    field = substr(str, 1, RLENGTH)
    gsub(/^[ \t]*"?|"?[ \t]*,$/, "", field)
    gsub(/""/, "\"", field)
    array[++i] = field
    str = substr(str, RLENGTH + 1)
  }
  return i
}' infile


4 commentaires

Merci, il semble que mon installation de Debian 6 n'utilise pas AWK 4, j'ai supposé que le paquet aurait une version plus récente de Awk


Vous pouvez essayer la solution perl que je viens d'ajouter.


Accepté et +1, comme je pense que c'est la meilleure solution, même si ce n'est pas celui que je peux utiliser dans ce cas


Bonjour @chrisbunney, j'ai ajouté la version qui devrait fonctionner avec votre version awk .



0
votes

en raison de la version légèrement dépassée de awk code> sur mon système et une préférence personnelle à coller à un script Bash, je suis arrivé une solution légèrement différente.

J'ai produit une utilité Script basé sur Ce blog post qui analyse le fichier CSV et Remplace les délimiteurs avec un délimiteur de votre choix afin que la sortie puisse être capturée et utilisée pour traiter facilement les données. Le script respecte les chaînes citées et les virgules intégrées, mais retirera les citations doubles qu'il trouve et ne fonctionnera pas avec des citations doubles évasées dans les champs. P>

#!/bin/bash

input=$1
delimiter=$2

if [ -z "$input" ];
then
    echo "Input file must be passed as an argument!"
    exit 98
fi

if ! [ -f $input ] || ! [ -e $input ];
then
    echo "Input file '"$input"' doesn't exist!"
    exit 99
fi

if [ -z "$delimiter" ];
then
    echo "Delimiter character must be passed as an argument!"
    exit 98
fi

gawk '{
    c=0
    $0=$0","                                   # yes, cheating
    while($0) {
        delimiter=""
        if (c++ > 0) # Evaluate and then increment c
        {
            delimiter="'$delimiter'"
        }

        match($0,/ *"[^"]*" *,|[^,]*,/)
        s=substr($0,RSTART,RLENGTH)             # save what matched in f
        gsub(/^ *"?|"? *,$/,"",s)               # remove extra stuff
        printf (delimiter s)
        $0=substr($0,RLENGTH+1)                 # "consume" what matched
    }
    printf ("\n")
}' $input


0 commentaires

1
votes

Si vous pouvez tolérer que les citations environnantes persistent dans la sortie, vous pouvez utiliser un petit script que j'ai écrit appelé CSVQuote pour activer AWK et couper (et d'autres outils de texte UNIX) pour gérer correctement les champs cités qui contiennent des virgules. Vous enveloppez la commande comme ceci: xxx

voir https://github.com/ dbro / csvquote pour le code et la documentation


0 commentaires

0
votes

Utilisation de la solution de Dimitire (merci pour cela) J'ai remarqué que son programme ignore les champs vides.

Voici le correctif: P>

awk '{ 
 for (i = 0; ++i <= NF;) {
   substr($i, 1, 1) == "\"" && 
     $i = substr($i, 2, length($i) - 2)
   printf "[%s]%s", $i, (i < NF ? OFS : RS)
    }   
 }' FPAT='([^,]*)|("[^"]+")' infile


0 commentaires