staging.inyokaproject.org

tabellen/csv dateien bearbeiten und zusammenfügen

Status: Gelöst | Ubuntu-Version: Ubuntu 10.04 (Lucid Lynx)
Antworten |

MisterIgo

Anmeldungsdatum:
23. April 2009

Beiträge: 947

Hallo,

ich habe zwei CSV-Dateien, die allerdings ziemlich groß sind. Aus diesen brauche ich aber nur ganz spezielle Daten. Um diese dennoch vernünftig bearbeiten zu können, wollte ich das über die Kommandozeile regeln. Die Dateien sind wie folgt aufgebaut: Datei1:

1
2
3
4
5
"id","egal1","egal2","egal3","wichtig","egal4","egal5","egal6","egal7","egal8","egal9"
"ZAHLENWERT1","bla","bla","bla","STRING1","bla","bla","bla","bla","bla","bla"
"ZAHLENWERT2","bla","bla","bla","STRING2","bla","bla","bla","bla","bla","bla"
"ZAHLENWERT3","bla","bla","bla","STRING4","bla","bla","bla","bla","bla","bla"
...

Also im Grunde Tabellenüberschrift und nachfolgend die Daten. Wichtig sind mir dabei nur die IDs und das was in der Spalte "wichtig" steht. Datei2:

"id","id2","wichtigzahl","egal10","egal11","egal12"
"ZAHLENWERT1","ZAHL_1","bla","bla","bla","bla"
"ZAHLENWERT1","ZAHL_2","bla","bla","bla","bla"
"ZAHLENWERT1","29","WICHTIG1","bla","bla","bla"
"ZAHLENWERT1","ZAHL_4","bla","bla","bla","bla"
"ZAHLENWERT2","ZAHL_1","bla","bla","bla","bla"
"ZAHLENWERT2","ZAHL_2","bla","bla","bla","bla"
"ZAHLENWERT3","ZAHL_1","bla","bla","bla","bla"
"ZAHLENWERT3","ZAHL_2","bla","bla","bla","bla"
"ZAHLENWERT3","29","WICHTIG2","bla","bla","bla"
"ZAHLENWERT4","ZAHL_1","bla","bla","bla","bla"
...

Ähnlich wie Datei1, nur das die ID in mehreren Zeilen vorkommen kann. Wichtig sind die Werte, die ich als "WICHTIG" gekennzeichnet habe und deren ID, also die Spalten die als ID2 29 haben. Die Anführungszeichen sind übrigens in den Dateien vorhanden.

Folgendes will ich erreichen: Die Felder von "wichtigzahl" aus Datei2 sollen den Strings aus Datei1 zugeordnet werden. Dazu wär ich jetzt so vorgegangen:

Schritt1: Nehme aus Datei2 alle Spalten mit der ID2 29 und speichere eine neue Datei (Datei3) in der nur noch die ID mit dem dazugehörigen "wichtigzahl"-Feld steht.

Schritt2: Gehe Datei3 zeilenweise durch und nimm den Wert von ID und vergleiche dieses mit Datei1. Aus der Zeile, wo der Wert vorkommt, entnimm den String. Speichere ID und String in einer neuen Datei.

Mir fehlt leider die Erfahrung beim Skripten. Könnte mir jemand eventuell Lösungsansätze für die Einzelschritte oder sogar das ganze Skript geben? Letzlich ist das ja Textverarbeitung, was mit der Shell ziemlich einfach geht.

mfG igo

track

Avatar von track

Anmeldungsdatum:
26. Juni 2008

Beiträge: 7174

Hi igo,

im Grunde brauchst Du ein tool das Dir die Zeilen nach Feldern auseinanderschnippelt, nicht wahr ?
Für solche Zwecke verwende ich selber awk, das ist zwar nicht das schnellste, aber ich finde es recht übersichtlich.

Der Haken liegt hier wie immer im Detail: Sind die einzelnen Datenfelder immer kommafrei ? - sonst muss man basteln.

Dann kannst Du als Trennzeichen ja das [,] einstellen und findest hinterher die Felder (bei awk) in den Variablen $1 ... wieder
und kannst dann damit machen was Du willst.

Ziemlich wichtig ist das was Du schon gemacht hast: in Prosa das "Pflichtenheft" aufzustellen - und dann daran entlang zu programmieren.

Du brauchst zum zwischenspeichern übrigens keine Datei, sowas geht auch ziemlich gut in einem Array.

Ich skizziere mal den 1. Schritt als awk-Skript (unter der Annahme dass die Datenfelder definitiv keine [,] enthalten):

#! /usr/bin/awk -f

BEGIN {	FS=",";				# stelle den "Field Separator" (= Trennzeichen) ein
	ix=1;
	}

{	if(FILENAME == "Datei 1") {
		if(FNR == 1) next;	# überspringe die 1. Zeile (ist ja die Überschrift)
		if($2 == "\"29\"") {	# Wenn im Feld 2 ["29"] steht ...
			feld[ix]= $3;	# dann speichere Feld 3 im Array
			ix++;		# und zähle den Index hoch.
			}
		}
	else if(FILENAME == "Datei 2") {
		if(FNR == 1) next;	# überspringe die 1. Zeile (ist ja die Überschrift)
					# hier kannst Du weiter machen ...
		}
	}

Auch hier liegen etliche Tücken in den Details, deshalb empiehlt es sich nicht nur, sich vorab mal schlau zu machen,
(Vorschlag dazu: http://www-e.uni-magdeburg.de/urzs/awk/ )
sondern auch das Skript in kleinen Schritten aufzubauen und immer wieder Probe-Ausgaben (mit print) zu machen.

LG,

track

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17630

Da Dein Beispiel nicht bis 29 geht suche ich nach WERT2, und gebe von den treffenden Zeilen -f 5, Feld 5 aus, -delimiter (Trennzeichen) ist das Komma, und analog für die 2. Datei:

grep WERT2 d1.csv | cut -d "," -f 5 
"STRING2"

- moment - Du willst mit Datei 2 anfangen, und da steht die 29...

Schritt1: Nehme aus Datei2 alle Spalten mit der ID2 29 und speichere eine neue Datei (Datei3) in der nur noch die ID mit dem dazugehörigen "wichtigzahl"-Feld steht.

  • also dann so, Datei 3 mal außen vor gelassen:

grep 29 d2.csv | cut -d "," -f 1,3 
"ZAHLENWERT1","WICHTIG1"
"ZAHLENWERT3","WICHTIG2"

Etwas geschwätzig:

for line in $(grep 29 d2.csv | cut -d "," -f 1,3)
do
    echo "line: " $line " "
    id=${line/\,*/}
    echo -n "id: " $id
    grep $id d1.csv | cut -d "," -f5 
done 

bzw.:

for line in $(grep 29 d2.csv | cut -d "," -f 1,3); do id=${line/\,*/}; echo -n $line","; grep $id d1.csv | cut -d "," -f5 ; done 

Wenn 29 noch an anderen Stellen i.d. Datei vorkommt klappt es so leicht nicht, auch nicht, wenn die ID 129 oder 920 sein kann. Dann könnten noch die Anführungsstriche retten, indem man nach "29" sucht, oder "000029" oder was.

Vielleicht klappt es aber - je nach dem, was in den anderen Spalten steht.

Mit awk kann man einfacher gezielt in Spalten suchen, während grep so erstmal ganze Zeilen untersucht.

In Scala sähe es so aus:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
val s1 = new java.util.Scanner (new java.io.File ("../d1.csv"))
val s2 = new java.util.Scanner (new java.io.File ("../d2.csv"))

def findIn1 (id: String, id2: String, imp1: String) : Unit = {               
	val line = s1.nextLine ()
	val ar = line.split (",")
	if (ar(0).equals (id)) {
		val imp2 = ar (4)
		println (id + "\t" + id2 + "\t" + imp1 + "\t" + imp2)
	} else findIn1 (id, id2, imp1) 
}

def findId2 (id2: String) : Unit = {                      
	val line = s2.nextLine ()                                
	val ar = line.split (",")
	if (ar(1).equals (id2)) {
		val id = ar (0)
		findIn1 (id, id2, ar(2)) 
	}
	else findId2 (id2) 
}

findId2 ("\"29\"")           

s2.close ()
s1.close ()

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17630

Eleganter wäre es natürlich, die Daten in eine (Postgre)sql-Datenbank zu bringen, dann sieht die Abfrage so aus:

– einmalig:

1
2
3
4
5
CREATE VIEW wichtig AS 
    (SELECT d2.id AS id, id2, wichtigzahl, 
            wichtig 
     FROM d1, d2
     WHERE d1.id = d2.id); 

Abfrage:

1
2
3
SELECT id, wichtigzahl, wichtig 
FROM wichtig
WHERE i2 = 29; 

Hello_World

Anmeldungsdatum:
13. Juni 2006

Beiträge: 3620

Mit dem Text::CSV::Slurp-Modul aus dem CPAN:

1
2
3
4
5
6
#!/usr/bin/perl
use Text::CSV::Slurp;
my $slurp = Text::CSV::Slurp->new;
my %zahlen = map { @{$_}{qw(id wichtigzahl)} } grep { $_->{id2} eq 29 } @{$slurp->load(file => $ARGV[0])};
my @foo = map { { wichtig => $_->{wichtig}, wichtigzahl => $zahlen{$_->{id}} } } grep { exists $zahlen{$_->{id}} } @{$slurp->load(file => $ARGV[1])};
say $slurp->create(input=>\@foo, field_order => [qw(wichtig wichtigzahl)]);

Als erstes Argument wird die Datei mit dem id2-Feld übergeben, als zweites die Datei mit den "wichtigen" Strings. Vermutlich heißen die Spalten nicht wirklich "wichtigzahl" und "wichtig", so dass das wahrscheinlich angepasst werden muss.

Kommentare von besseren Golfern sind willkommen, vermutlich kann man es noch verkürzen...

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4735

Falls ich das Problem richtig verstanden habe, hier ein Ansatz in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python
from __future__ import with_statement, division
import csv
import sys


def main():
    filename_1, filename_2 = sys.argv[1:3]
    
    with open(filename_2, 'rb') as file_2:
        data_2 = dict((row['id'], row['wichtigzahl'])
                      for row in csv.DictReader(file_2)
                      if row['id2'] == '29')
    
    with open(filename_1, 'rb') as file_1:
        for row in csv.DictReader(file_1):
            if row['id'] in data_2:
                print row['id'], data_2[row['id']], row['wichtig']


if __name__ == "__main__":
    main()

Es sieht aber IMHO nach einem Job für eine relationale Datenbank aus. Muss ja nicht gleich eine mit 'nem "fetten" Server sein – SQLite reicht vielleicht schon aus.

MisterIgo

(Themenstarter)

Anmeldungsdatum:
23. April 2009

Beiträge: 947

So, ich habe mir das jetzt zusammengebastelt. Dabei bin ich von dem ersten Ansatz von user unknown ausgegangen, vielen Dank. Danke natürlich auch den anderen. Meine Kommandozeile sieht jetzt so aus:

1
for line in $(cat id_und_rating.csv | egrep '^"[[:digit:]]*","29' | tr -s \",\" '@' | cut -d @ -f 2,4); do cat id_und_pfad.csv | tr -s \",\" '\t' | cut -f 2,6 | grep $(echo "$line" | cut -d @ -f1) | sed "s/$(echo "$line" | cut -d @ -f1)/$(echo "$line" | cut -d @ -f2)/g" ; done

Danach erhält man eine solche Ausgabe:

WICHTIG1\tSTRING1
WICHTIG2\tSTRING2
...

("\t" ist durch einen Tab zu ersetzen.)

mfG igo

Hello_World

Anmeldungsdatum:
13. Juni 2006

Beiträge: 3620

Das ist aber nicht konsistent mit den von Dir geposteten Beispieldaten, da müsste

WICHTIG1\tSTRING1
WICHTIG2\tSTRING4

herauskommen.

MisterIgo

(Themenstarter)

Anmeldungsdatum:
23. April 2009

Beiträge: 947

Stimmt, du hast recht, die Beispieldaten stimmen schon, aber mein Endergebnis, das ich gepostet hab, war falsch. Das müsste so aussehen wie du schon gesagt hast.

mfG igo

Hello_World

Anmeldungsdatum:
13. Juni 2006

Beiträge: 3620

Komischerweise kriege ich mit Deinem Shell-Befehl aber überhaupt nichts heraus. Naja, ist wohl auch nicht so wichtig...

Antworten |