128 votes

Comment analyser un fichier CSV en Bash?

Je travaille sur un long script Bash. Je veux lire des cellules d'un fichier CSV dans des variables Bash. Je peux analyser les lignes et la première colonne, mais pas les autres colonnes. Voici mon code jusqu'à présent :

  cat myfile.csv | while read line
  do
    read -d, col1 col2 < <(echo $line)
    echo "J'ai obtenu : $col1 | $col2"
  done

Il ne imprime que la première colonne. Pour un test supplémentaire, j'ai essayé ce qui suit :

read -d, x y < <(echo a,b,)

Et $y est vide. J'ai donc essayé :

read x y < <(echo a b)

Et $y est b. Pourquoi ?

7 votes

Avez-vous envisagé d'utiliser awk pour utiliser $1, $2, etc?

4 votes

En tant que note de côté: commande < <(echo "string") ---> commande <<< "string"

1 votes

Le programme en ligne de commande 'cut' a été conçu à cet effet : ss64.com/bash/cut.html

238voto

Dennis Williamson Points 105818

Vous devez utiliser IFS au lieu de -d:

while IFS=, read -r col1 col2
do
    echo "J'ai obtenu : $col1 | $col2"
done < myfile.csv

Pour sauter un nombre donné de lignes d'en-tête :

skip_headers=3
while IFS=, read -r col1 col2
do
    if ((skip_headers))
    then
        ((skip_headers--))
    else
        echo "J'ai obtenu : $col1 | $col2"
    fi
done < myfile.csv

Notez que pour l'analyse CSV à usage général, vous devriez utiliser un outil spécialisé capable de gérer les champs entre guillemets avec des virgules internes, parmi d'autres problèmes que Bash ne peut pas gérer seul. Des exemples de tels outils sont cvstool et csvkit.

9 votes

La solution proposée est adaptée pour les fichiers CSV très simples, c'est-à-dire si les en-têtes et les valeurs ne contiennent ni virgules ni guillemets imbriqués. Il est en fait assez difficile d'écrire un parseur CSV générique (surtout qu'il existe plusieurs "standards" CSV). Une approche pour rendre les fichiers CSV plus compatibles avec les outils *nix est de les convertir en TSV (valeurs séparées par des tabulations), par exemple en utilisant Excel.

0 votes

Il est intéressant que je ne peux pas faire mkdir dans le corps. Je reçois command not found. Seul le echo fonctionne.

1 votes

@Zsolt : Il n'y a aucune raison pour que cela soit le cas. Vous devez avoir une faute de frappe ou un caractère non imprimable errant.

11voto

dogbane Points 85749

À partir de la page man:

-d delim Le premier caractère de delim est utilisé pour terminer la ligne d'entrée, au lieu de saut de ligne.

Vous utilisez -d,, qui va terminer la ligne d'entrée sur la virgule. Il ne lira pas le reste de la ligne. C'est pourquoi $y est vide.

5voto

maithilish Points 316

Nous pouvons analyser les fichiers csv avec des chaînes entre guillemets et délimités par exemple | avec le code suivant

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 analyse les champs de chaîne en variables et tr supprime les guillemets.

Légèrement plus lent car awk est exécuté pour chaque champ.

1 votes

Bien, vous pouvez également utiliser la virgule (,)

2 votes

Traitement d'une ligne à la fois avec Awk est un motif grossier. awk -F'|' '{ gsub(/"/, ""); print $1, $2 }' "$csvFile"

2voto

F. Hauri Points 5893

Comment analyser un fichier CSV en Bash ? `

J'interviens tardivement sur cette question et en tant que bash offrent de nouvelles fonctionnalités, parce que cette question porte sur bash et parce qu'aucune des réponses déjà postées ne montre cette façon puissante et conforme de faire précisément ceci .

Parsing des fichiers CSV sous bash en utilisant module chargeable

Conforme à RFC 4180 une chaîne comme cet échantillon Ligne CSV :

12,22.45,"Hello, ""man"".","A, b.",42

devrait être divisé en

 1  12
 2  22.45
 3  Hello, "man".
 4  A, b.
 5  42

bash chargeable Modules compilés en .C.

Sous bash vous avez pu créer, modifier et utiliser chargeable c modules compilés . Une fois chargés, ils fonctionnent comme n'importe quel autre intégré ! ! ( Vous pouvez trouver plus d'informations sur arbre source . ;)

L'arbre des sources actuel (15 octobre 2021, bash V5.1-rc3) contient un tas d'échantillons :

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.

Il y a un travail complet cvs prêt à être utilisé dans examples/loadables répertoire : csv.c ! !

Sous Debian GNU/Linux vous devrez peut-être installer bash-builtins paquet par

apt install bash-builtins

Utilisation de bash-builtins chargeables :

Ensuite :

enable -f /usr/lib/bash/csv csv

A partir de là, vous pouvez utiliser cvs en tant que bash builtin .

Avec mon échantillon : 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

Puis dans une boucle, traitement d'un fichier.

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

Cette façon de faire est clairement la plus rapide et la plus solide que toute autre combinaison de bash builtins ou fork à tout binaire.

Malheureusement, selon l'implémentation de votre système, si votre version de bash a été compilé sans loadable cela peut ne pas fonctionner...

Exemple complet avec des champs CSV multilignes.

Voici un petit exemple de fichier avec 1 titre, 4 colonnes et 3 rangs. Parce que deux champs contiennent nouvelle ligne les fichiers sont 6 longueur des lignes.

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

Et un petit script capable d'analyser ce fichier correctement :

#!/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

Voici mon rendu : (j'ai utilisé printf "%q" pour représenter des caractères non imprimables comme nouvelles lignes comme $'\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"

Vous pourriez y trouver un échantillon de travail complet : csvsample.sh.txt ou csvsample.sh .

Attention :

Bien sûr, l'analyse des CSV en utilisant cette méthode n'est pas parfaite ! Cela fonctionne pour de nombreux fichiers CSV simples, mais attention à l'encodage et à la sécurité ! Par exemple, ce module ne sera pas capable de gérer les champs binaires !

Lire attentivement Commentaires sur le code source csv.c y RFC 4180 !

0 votes

Bien sûr, l'analyse csv sous bash n'est pas parfaite: csv chargeable ne pourra pas gérer les champs binaires et vous pourriez rencontrer des problèmes d'encodage et/ou de sécurité ... Lisez attentivement le RFC 4180 !!!

1voto

marcopeg Points 116

En plus de la réponse de @Dennis Williamson, il peut être utile de sauter la première ligne lorsqu'elle contient l'en-tête du CSV:

{
  read
  while IFS=, read -r col1 col2
  do
    echo "J'ai obtenu : $col1 | $col2"
  done 
} < myfile.csv

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X