web-dev-qa-db-fra.com

Script Bash Shell - Analyse CSV

J'essaie d'analyser un CSV contenant potentiellement 100k + lignes. Voici les critères que j'ai:

  1. L'index de l'identifiant
  2. La valeur de l'identifiant

Je voudrais récupérer toutes les lignes du CSV qui ont la valeur donnée dans l'index donné (délimité par des virgules).

Des idées, en tenant particulièrement compte de la performance?

33
tinkertime

Premier prototype utilisant les anciens grep et cut:

grep ${VALUE} inputfile.csv | cut -d, -f${INDEX}

Si c'est assez rapide et donne la sortie appropriée, vous avez terminé. :)

27
unwind

Comme alternative aux monolignes basées sur cut ou awk, vous pouvez utiliser le csvtool aka ocaml-csv:

$ cat yourfile | csvtool -t ',' col "$index" - | grep "$value"

Selon les documents, il gère les échappements, les citations, etc.

44
Andrey Vlasovskikh

Voir cette vidéo youtube: Leçon 10 de script BASH avec des fichiers CSV

Fichier CSV:

Bob Brown;Manager;16581;Main
Sally Seaforth;Director;4678;HOME

Script bash:

#!/bin/bash
OLDIFS=$IFS
IFS=";"
while read user job uid location
 do

    echo -e "$user \
    ======================\n\
    Role :\t $job\n\
    ID :\t $uid\n\
    SITE :\t $location\n"
 done < $1
 IFS=$OLDIFS

Sortie:

Bob Brown     ======================
    Role :   Manager
    ID :     16581
    SITE :   Main

Sally Seaforth     ======================
    Role :   Director
    ID :     4678
    SITE :   HOME
40
FRV

CSV n'est pas aussi simple que cela. Selon les limites des données dont vous disposez, vous devrez peut-être vous soucier des valeurs entre guillemets (qui peuvent contenir des virgules et des retours à la ligne) et des guillemets d'échappement.

Donc, si vos données sont suffisamment restreintes, vous pouvez vous contenter d'une simple séparation par virgule, le script Shell peut le faire facilement. Si, en revanche, vous devez analyser CSV "correctement", bash ne serait pas mon premier choix. Au lieu de cela, je regarderais un langage de script de niveau supérieur, par exemple Python avec un csv.reader .

12
bobince

Dans un fichier CSV, chaque champ est séparé par une virgule. Le problème est qu'un champ lui-même peut avoir une virgule intégrée:

Name,Phone
"Woo, John",425-555-1212

Vous avez vraiment besoin d'un package de bibliothèque qui offre une prise en charge CSV robuste au lieu de compter sur l'utilisation d'une virgule comme séparateur de champ. Je sais que les langages de script tels que Python a un tel support. Cependant, je suis à l'aise avec le langage de script Tcl donc c'est ce que j'utilise. Voici un script Tcl simple qui fait ce que vous demandez pour:

#!/usr/bin/env tclsh

package require csv 
package require Tclx

# Parse the command line parameters
lassign $argv fileName columnNumber expectedValue

# Subtract 1 from columnNumber because Tcl's list index starts with a
# zero instead of a one
incr columnNumber -1

for_file line $fileName {
    set columns [csv::split $line]
    set columnValue [lindex $columns $columnNumber]
    if {$columnValue == $expectedValue} {
        puts $line
    }   
}

Enregistrez ce script dans un fichier appelé csv.tcl et appelez-le en tant que:

$ tclsh csv.tcl filename indexNumber expectedValue

Explication

Le script lit le fichier CSV ligne par ligne et stocke la ligne dans la variable $ ligne, puis il divise chaque ligne en une liste de colonnes (colonnes $ variables). Ensuite, il sélectionne la colonne spécifiée et l'affecte à la variable $ columnValue. En cas de correspondance, imprimez la ligne d'origine.

9
Hai Vu

Utilisation de awk:

export INDEX=2
export VALUE=bar

awk -F, '$'$INDEX' ~ /^'$VALUE'$/ {print}' inputfile.csv

Edit: Selon Dennis Williamson's excellent commentaire, cela pourrait être écrit de façon beaucoup plus nette (et sûre) en définissant des variables awk en utilisant le -v commutateur:

awk -F, -v index=$INDEX -v value=$VALUE '$index == value {print}' inputfile.csv

Bon sang ... avec des variables, et tout, awk est presque n vrai langage de programmation ...

7
Nate Kohl

Pour les situations où les données ne contiennent aucun caractère spécial, la solution suggérée par Nate Kohl et ghostdog74 est bonne.

Si les données contiennent des virgules ou des sauts de ligne à l'intérieur des champs, awk peut ne pas compter correctement les numéros de champ et vous obtiendrez des résultats incorrects.

Vous pouvez toujours utiliser awk, avec l'aide d'un programme que j'ai écrit appelé csvquote (disponible sur https://github.com/dbro/csvquote ):

csvquote inputfile.csv | awk -F, -v index=$INDEX -v value=$VALUE '$index == value {print}' | csvquote -u

Ce programme trouve des caractères spéciaux dans les champs entre guillemets et les remplace temporairement par des caractères non imprimables qui ne confondent pas awk. Ensuite, ils sont restaurés une fois awk terminé.

4
D Bro
index=1
value=2
awk -F"," -v i=$index -v v=$value '$(i)==v' file
3
ghostdog74

Une solution sed ou awk serait probablement plus courte, mais en voici une pour Perl:

Perl -F/,/ -ane 'print if $F[<INDEX>] eq "<VALUE>"`

<INDEX> est basé sur 0 (0 pour la première colonne, 1 pour la deuxième colonne, etc.)

2
mob

Je cherchais une solution élégante qui prend en charge les devis et ne nécessiterait rien d'installation de fantaisie sur mon appliance VMware vMA. Il s'avère que ce simple script python fait l'affaire! (J'ai nommé le script csv2tsv.py, car il convertit CSV en valeurs séparées par des tabulations - TSV)

#!/usr/bin/env python

import sys, csv

with sys.stdin as f:
    reader = csv.reader(f)
    for row in reader:
        for col in row:
            print col+'\t',
        print

Les valeurs séparées par des tabulations peuvent être facilement divisées avec la commande cut (aucun délimiteur ne doit être spécifié, tab est la valeur par défaut). Voici un exemple d'utilisation/sortie:

> esxcli -h $VI_Host --formatter=csv network vswitch standard list |csv2tsv.py|cut -f12
Uplinks
vmnic4,vmnic0,
vmnic5,vmnic1,
vmnic6,vmnic2,

Dans mes scripts, je vais en fait analyser la sortie tsv ligne par ligne et utiliser read ou cut pour obtenir les champs dont j'ai besoin.

1