2010-10-15 7 views
4

Salut J'ai une tonne de données dans plusieurs fichiers csv et filtrer un ensemble de données en utilisant grep:Tri des colonnes csv en bash, lire la sortie de bash en variables python

[email protected]:~/$ cat data.csv | grep -a "63[789]\...;" 
637.05;1450.2 
637.32;1448.7 
637.60;1447.7 
637.87;1451.5 
638.14;1454.2 
638.41;1448.6 
638.69;1445.8 
638.96;1440.0 
639.23;1431.9 
639.50;1428.8 
639.77;1427.3 

Je veux comprendre l'ensemble de données qui a le compte le plus élevé, la colonne à droite du; et puis connaître la valeur correspondante (à gauche de la;). Dans ce cas, l'ensemble je suis à la recherche serait 638,14; 1454,2

J'ai essayé différentes choses et a fini en utilisant une combinaison de bash et python, qui fonctionne, mais est pas très jolie:

os.system('ls | grep csv > filelist') 
files = open("filelist") 
files = files.read() 
files = files.split("\n") 

for filename in files[0:-1]: 
    os.system('cat ' + filename + ' | grep -a "63[6789]\...;" > filtered.csv') 
    filtered = csv.reader(open('filtered.csv'), delimiter=';') 
    sortedlist = sorted(filtered_file, key=operator.itemgetter(1), reverse=True) 
    dataset = sortedlist[0][0] + ';' + sortedlist[0][1] + '\n' 

J'aimerais avoir une solution de bash seulement (couper, awk, tableaux?!?) Mais je ne pouvais pas le comprendre. De plus, je n'aime pas travailler sur l'écriture des commandes bash dans des fichiers, puis les lire dans des variables python. Puis-je les lire directement dans les variables ou y a-t-il de meilleures solutions à ce problème? (probablement perl etc ... mais je suis vraiment intéressé par une solution bash ..)

Merci beaucoup !!

Répondre

3

Si vous utilisez Python, utilisez Python. Pourquoi mélangez-vous les commandes bash ensemble? Cela rend votre code non portable/dépendant d'un environnement bash.

import os 
import glob 
import operator 
os.chdir("/mypath") 
for file in glob.glob("*.csv"): 
    data=open(file).readlines() 
    data=[i.strip().split(";") for i in data if i[:3] in ["637","638","639"]] 
    # data=[i.strip().split(";") for i in data if i[:3] in ["637","638","639"] and isinstance(float(i[:6]),float) ] 
    sortedlist = sorted(data, key=operator.itemgetter(1), reverse=True) 
    print "Highest for file %s: %s" % (file,sortedlist[0]) 

OU, si vous êtes plus intéressé par un bash + outils solution

find . -type f -name '*.csv' |while read -r FILE 
do 
grep -a "63[789]\...;" "$FILE" | sort -n -r -t ';' -k 2 | head -1 >> output.txt 
done 
+0

merci c'est un très bon script, mais le filtre pour 637, 638 et 639 ne vérifie pas l'expression rationnelle \ ...; est-ce facilement possible avec python? ce que je viens de remarquer en l'exécutant, c'est le "" autour du fichier dans les données = ligne ouverte .. merci encore j'aime vraiment cet extrait – gletscher

+0

Si vous voulez vraiment vérifier en utilisant regex, vous pouvez utiliser le module 're'. Sinon, vous pouvez simplement vérifier si c'est un flottant. voir mon edit. – ghostdog74

+0

+1, si vous pensez avoir besoin de frankenscripts, vous ne connaissez probablement pas assez bien les environnements (bash ou python). Je suis souvent coupable de cela. – Thomas

6

Une seule ligne rapide serait:

grep -a "63[789]\...;" data.csv | sort -n -r -t ';' -k 2 | head --lines=1 

Cette trie simplement le fichier numérique en fonction de la deuxième colonne, puis imprime la première ligne. J'espère que cela pourra aider.

+0

Est-ce que '-r' et 'head' ont plus d'avantages que d'omettre' -r' et d'utiliser 'tail'? – Wrikken

+0

S'il y avait beaucoup de données entre les deux commandes, il serait plus rapide de lire à partir de la tête puis de la queue. –

1
$ cat data.csv | grep -a "63[789]\...;" | awk 'BEGIN {FS=";"} $2>max{max=$2; val=$1} END {print "max " max " at " val}' 

max 1454.2 at 638.14 
+0

Merci ars, cette ligne fonctionne avec les données affichées ci-dessus, mais j'ai encore besoin de filtrer l'ensemble de données en utilisant grep "63 [89] \ ...;", j'ai essayé de l'intégrer, mais ça ne marche pas. Cette ligne ne trouvera pas de numéro sur le csv car elle contient un en-tête etc ... – gletscher

+0

@gletscher: on dirait que vous avez trouvé une solution, mais j'ai mis à jour la réponse pour montrer comment vous la rediriger vers awk (qui fonctionne de ma part). – ars

+0

merci beaucoup pour la mise à jour, il trouve l'emplacement correct, mais n'affiche pas le résultat correctement, la sortie est: à 638,14 aussi j'essaye de boucler tous les fichiers dans le répertoire et de recueillir tous les ensembles de données filtrées – gletscher

0

gentil, merci beaucoup, Hakop Palyan !!

Maintenant, est-il un truc sur la façon d'obtenir cet ensemble de données sur tous les fichiers CSV et le recueillir quelque part comme un nouveau fichier? quelque chose comme

find . -name '*.csv' -print0 | xargs -0 grep -a "63[789]\...;" | sort -n -r -t ';' -k 2 | head --lines=1 

celui-ci imprime uniquement la première ligne, il faudrait que j'itérer sur les fichiers individuels et de recueillir les ensembles de données ...

+0

Vous devriez soit poser cette question séparément, soit mettre à jour votre question initiale. – istruble

1

Si vous avez une tonne de données alors vous n » t veulent stocker toutes ces données en mémoire, puis sortez pour obtenir la valeur maximale. Cette approche est inefficace concernant à la fois la complexité du temps de calcul et la mémoire.

Vous pouvez simplement analyser les fichiers et calculer les valeurs souhaitées à la volée.Une rapide approche Python pur pour traiter votre problème:

import os, re 
os.chdir('/path/to/csvdir') 
for f in os.listdir('.'): 
    dataset, count = 0.0, 0.0 
    for line in open(f): 
     if re.search(r'63[6789]\...', line): 
      d, c = map(float, line.strip().split(';')) 
      if count < c: 
       dataset, count = d, c 
    print f, dataset 

Cette approche peut également être utilisé pour afficher une liste de valeurs max (s'il peut y avoir plus d'un ensemble de données avec comptes les plus élevés) en modifiant le approprié lignes:

dataset, count = [], 0.0 
... 
     if count < c: 
      dataset, count = [d], c 
     elif count == c: 
      dataset.append(d) 

Edit: le script suppose que votre csvdir est peuplé uniquement avec des fichiers contenant le format d'analyse syntaxique. Si vous voulez les filtrer par nom, vous pouvez utiliser glob (avec des capacités regexp limitées dans le filtrage du nom):

for f in glob.glob('*.csv'): 

ou appliquez un filtre à os.listdir:

for f in filter(lambda f: re.match('.*\.csv', f), os.listdir('.')): 
+0

hé c'est un script super sympa, bien que le script se transforme en erreur si d'autres fichiers sont présents dans le dossier, donc en utilisant ghostdogs74 glob.glob ("*. Csv"): au lieu de os.listdir (".") Semble fonctionner mieux dans ce cas, merci beaucoup – gletscher

+0

merci pour le filtre regexp – gletscher

1

Voici le code que j'ai écrit trier les fichiers csv en utilisant python. Il vous permet de spécifier plusieurs colonnes et de trier dans l'ordre inverse en utilisant un signe moins.

#!/usr/bin/env python 
# Usage: 
# (1) sort ctb_consolidated_test_id.csv by Academic Year, Test ID, Period, and Test Name, with Test ID in descending order 
# sort_csv.py -c "Academic Year" -c "-Test ID" -c "Period" -c "Test Name" ctb_consolidated_test_id.csv 
from __future__ import with_statement 
from __future__ import print_function 

import sys 

def multikeysort(items, columns): 
    from operator import itemgetter 
    import re 
    num_re = re.compile(r'^\d+$') 
    comparers = [ 
     ((itemgetter(col[1:].strip()), -1) if col.startswith('-') else (itemgetter(col.strip()), 1)) 
     for col in columns 
    ] 
    def number_comparable(val1, val2): 
     return len(val1) != len(val2) and num_re.match(val1) and num_re.match(val2) 
    def column_comparer(left, right): 
     for fn, mult in comparers: 
      val1, val2 = fn(left), fn(right) 
      if number_comparable(val1, val2): 
       val1, val2 = int(val1), int(val2) 
      result = cmp(val1, val2) 
      if result: 
       return mult * result 
     return 0 
    return sorted(items, cmp=column_comparer) 

def sort_csv(filename, columns): 
    import csv 
    with open(filename, "r") as f: 
     reader = csv.DictReader(f) 
     writer = csv.DictWriter(sys.stdout, reader.fieldnames) 
     writer.writerow(dict(zip(reader.fieldnames, reader.fieldnames))) 
     writer.writerows(multikeysort(reader, columns)) 

if __name__ == '__main__': 
    from glob import glob 
    from optparse import OptionParser, make_option 
    option_list = [ 
     make_option('-c', '--column', dest='columns', action='append', metavar='COLUMN NAME'), 
    ] 
    parser = OptionParser(option_list=option_list) 
    (options, args) = parser.parse_args() 
    filenames = (filename for arg in args for filename in glob(arg)) 
    for filename in filenames: 
     sort_csv(filename, options.columns) 
0

Je sais que vous êtes à la recherche d'une solution à base de bash, mais je ne pouvais pas aider à offrir quelque chose en utilisant le module csv.

import os 
import csv 
import re 

target_re = re.compile(r'^63[789]\.\d\d$') 
csv_filenames = [f for f in os.listdir('.') if f.endwith('.csv')] 
largest_in_each_file = [] 

for f in csv_filenames: 
    largest = (None, 0) 
    for a,b in csv.reader(open(f, 'rb'), delimiter=';'): 
     if target_re.match(a) and b > largest[1]: 
      largest = (a, b) 
    largest_in_each_file.append(largest) 


largest_overall = largest_in_each_file[0] 
for largest in largest_in_each_file: 
    print "%s;%s in %s" % largest 
    if largest[1] > largest_overall[1]: 
     largest_overall = largest 

print "-" * 10 
print "%s;%s in %s is the largest record in all files" % largest_overall