|
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:
| "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
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
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.
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
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:
| CREATE VIEW wichtig AS
(SELECT d2.id AS id, id2, wichtigzahl,
wichtig
FROM d1, d2
WHERE d1.id = d2.id);
|
Abfrage: | 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:
| #!/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
Ehemalige
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:
| 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
|
|
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
|