• Willkommen im Linux Club - dem deutschsprachigen Supportforum für GNU/Linux. Registriere dich kostenlos, um alle Inhalte zu sehen und Fragen zu stellen.

CSV Datei aufsplitten in mehrere Files

Flash

Member
Hallo Leute,

ich hab eine CSV Datei mit folgendem Format
Code:
"Unwichtiger Text"
"Unwichtiger Text"
"Spalte1","Typ","Spalte3","Spalte4"
"1234","TYP-A","Text","Anderes Zeug"
"1256","TYP-B","Text","Anderes Zeug"
"1333","TYP-C","Text","Anderes Zeug"
"6421","TYP-B","Text","Anderes Zeug"
"9898","TYP-A","Text","Anderes Zeug"

Frage 1
Ich möchte nun gern alle Zeilen die den TYP-A haben in eine Datei aussortieren, die mit TYP-B in eine andere Datei, die mit TYP-C in eine dritte.
Wie geht das?
Ich könnte eine Liste aller Werte die in der TYP Spalte auftreten können in einem extra Textfile bereit stellen.

Frage 2
Darauf Aufbauend habe ich dann aber den Bedarf, verschiedene Files die solche Inhalte haben in den Ergebnis Files zusammen zu führen.
Es gibt als 2 oder 10 solche CSV Files und die können alle Einträge von Typ TYP-A haben. Ich will das alle Einträge mit TYP-A aus allen Files in einem File zusammen geführt werden. Sortieren und auswerten kann ich dann mit den Tabellen-Kalkulationsprogrammen.
 

framp

Moderator
Teammitglied
Dazu eignet sich grep perfekt. Voraussetzung: Der Text ,"TYP-A", usw kommt sonst nicht noch anders in den Dateien vor. Ansonsten muss man einen komplexeren regulären Ausdruck zusammenbauen. Ich denke aber so sollte es ausreichen.
Code:
grep ',"TYP-A",' * > TYPA.csv
grep ',"TYP-B",' * > TYPB.csv
grep ',"TYP-C",' * > TYPC.csv
 
Frage 1
Probiere das aus (ich habe es nicht getestet)

Code:
#! /bin/bash

TPA="/tmp/type_a"
TPB="/tmp/type_b"
TPC="/tmp/type_c"

parse_type()
{
	 local arr ct=1 fn="$1"
	 while IFS= read -r; do
		 IFS=$','
		 arr=($REPLY)
		 printf "[%.3d] %s\n" "$ct" "$REPLY"
		 case "${arr[1]}" in
 		 "TYP-A")
	 			 printf "%s\n" "$REPLY" >> "$TPA"
	 			 ;;
		 "TYP-B")
	  			printf "%s\n" "$REPLY" >> "$TPB"
	  			;;
 		"TYP-C")
	 			 printf "%s\n" "$REPLY" >> "$TPC"
	  			;;
 	esac
	 ((ct++))
	 done < "$fn"
	 return 0
}

[ -f "$1" ] || exit 1
#rm -f "$TPA"
#rm -f "$TPB"
#rm -f "$TPC"
parse_type "$1"
exit 0

Abspeichern als z.B. test.sh

# test.sh "/path/das_noch_undecodierte_file"

Frage 2 verstehe ich nicht bzw. wird jemand anderer auflösen

Gruß
Gräfin Klara
 

framp

Moderator
Teammitglied
Anbei eine noch kompaktere Version die gleich die Ausgabedateien automatisch für alle TYP-x erstellt :D
Code:
for t in TYP-A TYP-B TYP-C; do echo "Creating $t.csv ...";egrep -E "[^,]+,\"$t\"," * > $t.csv; done
 
framp schrieb:
Anbei eine noch kompaktere Version die gleich die Ausgabedateien automatisch für alle TYP-x erstellt :D
Code:
for t in TYP-A TYP-B TYP-C; do echo "Creating $t.csv ...";egrep -E "[^,]+,\"$t\"," * > $t.csv; done

HaHa, Ja, das ist kompakt
 
OP
F

Flash

Member
Danke für die Antworten. Hier noch ein paar Infos und Fragen:

Frage 2 ist so zu verstehen, dass ich nicht nur ein eingabe-file habe sondern mehrere.
Ich glaube die Lösung sieht irgendwie so aus: (Pseudocode wobei irgendwie $datei1 und $datei2 übergeben werden)
Code:
for t in Leerzeichen getrennte liste aller typen; 
do 
    echo "Creating $t.csv ...";
    echo "schreibe Header";
    grep "Spalte1" $datei1 > $t.csv;
    
    echo "schreibe zeilen aus datei 1";
    grep $t $datei1 >> $t.csv;
    
    echo "schreibe zeilen aus datei 2"; 
    grep $t $datei2 >> $t.csv;
done

Wie bekomme ich $datei1 bzw $datei2 übergeben? Ich bau mir gerade ein shell script was die Werte zusammen sammelt.
 
OP
F

Flash

Member
Idealerweise ist das ganze nicht limitiert auf 2 dateien. Sondern 2..n dateien.
 
OP
F

Flash

Member
Ah.... da ist ja ein kleines "*" im grep. Hab ich übersehen.

Ok, dann probier ich mal mein Script aus.
 
OP
F

Flash

Member
Ok, habs ausprobiert. Es gibt noch ein kleines Problem mit dem "*" Modus.
Und zwar schreibt grep das >>FileName.typ:<< direkt vor die Grep ausgabe. Kann man das formatieren? z.B. dass statt dem : ein , verwendet wird (csv conform)
 
OP
F

Flash

Member
Wenn ich das hier ausführe: foo@bar:~/Downloads$ grep Transaktionen *.txt > foobar.txt

dann sieht das resultat in foobar.txt so aus:
Code:
report (10).txt:Transaktionen: 30								
report (11).txt:Transaktionen: 66
report (12).txt:Transaktionen: 27
report (13).txt:Transaktionen: 42
report (14).txt:Transaktionen: 37
report (15).txt:Transaktionen: 55
report (16).txt:Transaktionen: 55								
report (17).txt:Transaktionen: 76
report (18).txt:Transaktionen: 51
report (19).txt:Transaktionen: 75
report (1).txt:Transaktionen: 14
report (20).txt:Transaktionen: 76
report (21).txt:Transaktionen: 82
report (22).txt:Transaktionen: 112								
report (23).txt:Transaktionen: 113											Verkauf
report (24).txt:Transaktionen: 74
report (25)_020816-250816.txt:Transaktionen: 186									
report (25).txt:Transaktionen: 126
report (26).txt:Transaktionen: 142
report (27).txt:Transaktionen: 166
report (28).txt:Transaktionen: 167
report (29).txt:Transaktionen: 160
report (2).txt:Transaktionen: 12
report (30).txt:Transaktionen: 92									
report (31).txt:Transaktionen: 187
report (32).txt:Transaktionen: 236
report (33).txt:Transaktionen: 485
report (3).txt:Transaktionen: 22
report (4).txt:Transaktionen: 15
report (5).txt:Transaktionen: 31
report (6).txt:Transaktionen: 2
report (7).txt:Transaktionen: 37
report (8).txt:Transaktionen: 11
report (9).txt:Transaktionen: 29
report.txt:Transaktionen: 18

die Dateinamen werden mit ausgegeben. Ich möchte da gern einen anderen Trenner hinter dem Dateinamen haben. Statt : halt , oder gar kein Dateiname.
 

framp

Moderator
Teammitglied
Wo kommen jetzt die Transaktionen her? Das passt nicht zu Deiner Eingangsfrage :Kopfkratz:

BTW: Ich habe Deinen vorherigen Post für Dich anonymisiert und foo@bar als System und Benutzer eingesetzt :)
 
OP
F

Flash

Member
Das ist jetzt einfach nur eine Beispielausgabe. Hmmmm...

Ich versuchs mal anders. Wenn du grep mit * aufrufst sieht die ausgabe immer so aus
<dateiname>.<endung>:<zeile die durch das suchmuster gefunden wurde>

Was ich will ist:
<dateiname>.<endung>,<zeile die durch das suchmuster gefunden wurde>
 

framp

Moderator
Teammitglied
Das verstehe ich jetzt :)

Um diese Ausgabe zu erhalten muss ich noch -H als Parameter bei grep angeben. Erst der bewirkt bei mir die Ausgabe des Dateinamens von dem Match. Das entspricht zwar nicht mehr Deinen Eingangsanforderungen aber das läßt sich auch leicht lösen :fies:

Hänge einfach einen Pipe zu sed dran wo Du das erste : durch ein , ersetzt ;)

Code:
| sed 's/:/,/'
 
OP
F

Flash

Member
Das scheint nicht zu funktionieren. An der Ausgabe ändert es nichts. (Liegt das daran, dass vor dem : definitiv kein Space ist?)

Mein Script sieht aktuell so aus:
Code:
for t in Leerzeichen Separierte Liste; 
do 
    echo "Creating $t.csv ...";
    echo "schreibe Header";
    echo '"Datum/Uhrzeit","Typ","Beschreibung","Gesamt"' > $t.csv;
    
    echo "schreibe zeilen aus dateien";
    grep $t * >> $t.csv | sed 's/:/,/';
    
done

Das Ergebnis sieht so aus:
Code:
"Datum/Uhrzeit","Typ","Beschreibung","Gesamt"
wasAnderes.csv:"28.12.2015 13:05:53 GMT+00:00","FooBar12gg","Item Foo Bar 12 gg","3,18"
 

framp

Moderator
Teammitglied
Code:
grep $t * >> $t.csv | sed 's/:/,/';
ist falsch. Da schreibst Du das grep Ergebnis in die Datei ohne es vorher per sed zu ändern.
Code:
grep $t * | sed 's/:/,/' >> $t.csv
wird aber funktionieren ;)

PS: Warum benutzt Du >> ? Ich würde > benutzen.
 
OP
F

Flash

Member
Ich meld mich mal zurück. Eins vorweg, Perl ist keine Option. Ich will zumindest grob verstehen was passiert. ;)

Ich benutze >> statt > weil sonst der Header verschwindet den ich vorher in die Datei geschrieben habe.

Das Script funktioniert ganz gut. Allerdings würde ich nun gern etwas gewagtes versuchen.

die CSV Zeilen sehen ungefähr so aus
Code:
"asdasd_asdasda_dasdas","TYP-A","000-123213-4121231","fhjkasdasdsa asdas asdasda","12","35678","fhjkasdasdsa asdas asdasda"
"avgask_fsaasda_dasdas","TYP-A","000-123213-4121231","fhjkasdasdsa asdas asdasda","13","35678","fhjkasdasdsa asdas asdasda"
"asdasd_asdasda_dasdas","TYP-B","000-123213-4121231","fhjkasdasdsa asdas asdasda","2","35678","fhjkasdasdsa asdas asdasda"
"asdasd_asdasda_dasdas","TYP-A","000-123213-4121231","fhjkasdasdsa asdas asdasda","222","35678","fhjkasdasdsa asdas asdasda"

Ich möchte nun gern folgendes machen: Wenn in der Spalte mit TYP- ein "TYP-B" steht, dann soll die Zahl 3 Spalten weiter hinten negiert werden. Also im Fall oben aus der 2 soll eine -2 werden.
Ich hatte mir überlegt, man könnte ja einen RegEx Matcher bauen der ab TYP-B sucht und durch ein geschicktes Muster genau das erreicht. Nur ist mein RegEx doch zu schwach.
 

abgdf

Guru
Flash schrieb:
Ich meld mich mal zurück. Eins vorweg, Perl ist keine Option. Ich will zumindest grob verstehen was passiert. ;)
...
Ich möchte nun gern folgendes machen: Wenn in der Spalte mit TYP- ein "TYP-B" steht, dann soll die Zahl 3 Spalten weiter hinten negiert werden. Also im Fall oben aus der 2 soll eine -2 werden.
Ich hatte mir überlegt, man könnte ja einen RegEx Matcher bauen der ab TYP-B sucht und durch ein geschicktes Muster genau das erreicht. Nur ist mein RegEx doch zu schwach.
Schade, wäre alles kein Problem:
Code:
#!/usr/bin/perl

use warnings;
use strict;

my $DATA = <<'END_DATA';
sdsa asdas asdasda","12","35678","fhjkasdasdsa asdas asdasda"
"avgask_fsaasda_dasdas","TYP-A","000-123213-4121231","fhjkasdasdsa asdas asdasda","13","35678","fhjkasdasdsa asdas asdasda"
"asdasd_asdasda_dasdas","TYP-B","000-123213-4121231","fhjkasdasdsa asdas asdasda","2","35678","fhjkasdasdsa asdas asdasda"
"asdasd_asdasda_dasdas","TYP-A","000-123213-4121231","fhjkasdasdsa asdas asdasda","222","35678","fhjkasdasdsa asdas asdasda"
END_DATA

my @a = split(/\n/, $DATA);
my $i;
foreach $i (@a) {
    if ($i =~ /TYP-B/) {
        my @b = split(/,/, $i);
        my $num = substr($b[4], 1);
        chop($num);
        $num = $num * -1;
        $b[4] = '"' . $num . '"';
        $i = join(",", @b);
    }
    print "$i\n";
}
 
Oben