|
skapi85
Anmeldungsdatum: 19. August 2017
Beiträge: 48
|
Hallo, ich habe folgendes Problem: Ich habe eine CSV Datei, wo ab Zeile 8, am Zeilenanfang eine Korrektur gemacht werden soll.
Es sollen die Großbuchstaben C,F & T am Zeilenanfang (ab Zeile 8) entfernt werden.
Folgendes hab ich schon Probiert, ist aber nicht ideal...
| sed 's/\T//'/Users/m/trades.csv
|
Würde mich freuen wenn ich hierzu eine Unterstützung bekommen könnte ☺ Vielen Dank im Voraus!
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
Generell würde ich davon abraten CSV mit zeilenweise basierten Tools zu bearbeiten, aber wenn Quoting und Zeilenumbrüche in Feldern kein Problem sind, dann sollte das so mit sed gehen:
8,$ Wende den nachfolgenden Befehl auf die 8. Zeile bis zum Ende der Datei an
s/^[CFT]/ Ersetze C, F oder T am Anfang einer Zeile durch einen leeren String
$ sed '8,$s/^[CFT]//' << EOF
A,B,C
FD,E,F
G,H,I
J,K,L
M,N,O
P,Q,R
S,T,U
TV,W,Z
FNORD,0,1
EOF
A,B,C
FD,E,F
G,H,I
J,K,L
M,N,O
P,Q,R
S,T,U
V,W,Z
NORD,0,1
|
|
skapi85
(Themenstarter)
Anmeldungsdatum: 19. August 2017
Beiträge: 48
|
Danke!!
Ich habe einen Fehler in der Beschreibung! Das erste Zeichen jeder Zeile ist ein " gefolgt von einer Zeichenkette aus Kleinbuchstaben, ausser die zu korrigierenden Zeilen fangen mit einem Großbuchstaben (C,F,T) an. Bsp.:
| "Ce155a09d-... -> Nicht OK
"dbf255fd-... -> OK
|
Korrekterweise müsste es das zweite Zeichen je Zeile sein (ab Zeile 8).
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
Dann musst du den Ersetzungsbefehl anpassen:
Bei csv mit Quoting würde ich tendenziell eher zu einem richtigen Parser greifen (Python hat z.B. ein brauchbares csv Modul).
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17630
|
seahawk1986 schrieb: Dann musst du den Ersetzungsbefehl anpassen:
Bei csv mit Quoting würde ich tendenziell eher zu einem richtigen Parser greifen (Python hat z.B. ein brauchbares csv Modul).
Dann zeig doch mal den Pythoncode, den Du für die Aufgabe brauchst, und erkläre, was daran dann besser ist. Das ist doch hier ein Bilderbuchbeispiel für den sinnvollen Einsatz von sed. Ob mit oder ohne Quoting ist vollkommen irrelevant und CSV ist generell für sed meist sehr geeignet, weil es fast immer zeilenweise strukturiert ist.
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
user_unknown schrieb: weil es fast immer zeilenweise strukturiert ist.
Eben, damit ist sed keine allgemeingültige Lösung. Das ist das generelle Problem bei den ganzen zeilenbasiert arbeitenden CLI-Tools, dass man sich sehr einfach Daten versauen kann - es kann genügen, dass die Zeilennummern verrutschen, wenn es einen Zeilenumbruch innerhalb eines gequoteten Feldes gibt (oder etwas abstruser, wenn sich jemand bei den Delimitern oder Quotechars austobt). Einfaches Beispiel, das das Problem illustriert:
| "1",one
"2",two
"3",three
"4",four
"5",five
"6","six
and a little extra"
"T<-Das T am Anfang der Zeile sollte eigentlich erhalten bleiben",seven
"C8",eight
|
$ '8,$s/^"[CFT]/"/' test.csv
"1",one
"2",two
"3",three
"4",four
"5",five
"6","six
and a little extra"
"<-Das T am Anfang der Zeile sollte eigentlich erhalten bleiben",seven
"8",eight
Mit Python könnte das z.B. so aussehen, wenn man die Datei das Ergebnis auf stdout rausschreiben will:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | #!/usr/bin/env python3
import csv
import io
import sys
for path in sys.argv[1:]:
with (open(path, 'r', newline='') as f,
open(sys.__stdout__.fileno(),
mode=sys.__stdout__.mode,
buffering=1,
encoding=sys.__stdout__.encoding,
errors=sys.__stdout__.errors,
newline='',
closefd=False) as tmp):
reader = csv.reader(f)
writer = csv.writer(tmp, lineterminator='\n')
for n, (first, *other) in enumerate(reader, start=1):
if n >= 8 and first.startswith(('C', 'F', 'T')):
first = first[1:]
writer.writerow([first, *other])
|
Damit funktioniert die Zeilennummerierung zuverlässig.
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17630
|
seahawk1986 schrieb: user_unknown schrieb: weil es fast immer zeilenweise strukturiert ist.
Eben, damit ist sed keine allgemeingültige Lösung. Das ist das generelle Problem bei den ganzen zeilenbasiert arbeitenden CLI-Tools, dass man sich sehr einfach Daten versauen kann - es kann genügen, dass die Zeilennummern verrutschen, wenn es einen Zeilenumbruch innerhalb eines gequoteten Feldes gibt (oder etwas abstruser, wenn sich jemand bei den Delimitern oder Quotechars austobt). Einfaches Beispiel, das das Problem illustriert:
| "1",one
"2",two
"3",three
"4",four
"5",five
"6","six
and a little extra"
"T<-Das T am Anfang der Zeile sollte eigentlich erhalten bleiben",seven
"C8",eight
|
$ '8,$s/^"[CFT]/"/' test.csv
"1",one
"2",two
"3",three
"4",four
"5",five
"6","six
and a little extra"
"<-Das T am Anfang der Zeile sollte eigentlich erhalten bleiben",seven
"8",eight
Tja, CSV-Daten bearbeitet man ja häufig wiederkehrend und weiß, ob sie Zeilenumbrüche in Quotes enthalten können oder nicht. Oft produziert man sie ja selbst als Exportdaten aus Datenbanken und kann Zeilenumbrüche in der Stringmitte ausschließen. Du müsstest jetzt noch ein Tool benennen können, das eine CSV-Datei obiger Form produziert.
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
user_unknown schrieb: Du müsstest jetzt noch ein Tool benennen können, das eine CSV-Datei obiger Form produziert.
Viele Programme für Tabellenkalkulation, wenn man Zellen mit Zeilenumbruch hat und dann eine Tabelle als csv exportiert - z.B.:
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17630
|
Und man erinnert sich dann nicht daran, dass man Zeilenumbrüche in der Tabellenzelle hat, aber einen CSV-Export eingerichtet. Ja, es gibt auch schwarze Schwäne und roten Hering ...
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
Solche CSV-Dateien kommen gerne mal von Dritten, denen das völlig egal ist und die nur die Anforderung erfüllen wollen, dass sie eine CSV-Datei schicken sollen. Und das kann man ihnen nicht übel nehmen, der Standard (RFC4180 Abschnitt 2.6) erlaubt das ja: 6. Fields containing line breaks (CRLF), double quotes, and commas
should be enclosed in double-quotes. For example:
"aaa","b CRLF
bb","ccc" CRLF
zzz,yyy,xxx
Wenn man solche Daten mit einem Programm weiterverarbeitet, das das nicht berücksichtigt und stattdessen über die Wahrscheinlichkeit von Edge-Cases lamentiert, fällt das für mich unter Schlamperei - nur wenn man absolut sicher sein kann, dass das nicht vorkommen kann (weil sich alle Beteiligten an die Konvention halten bzw. man vorab die Daten validiert), kann das gerechtfertigt sein, wobei ich dann alles daran setzen würde, gequotete Felder zu vermeiden, sonst macht das mit gängigen Tools wie awk, cut usw. keinen Spaß - und dann landet man schnell bei Tab-separated_values, was oft das bequemere Format für Daten in Tabellenform ist, wenn man die Zeilen- und Feldweise in der Shell verarbeiten will.
|
|
user_unknown
Anmeldungsdatum: 10. August 2005
Beiträge: 17630
|
Ich habe CSV generell als überspezifische Bezeichnung für x-separated values kennengelernt, meist per Semikolon oder Tabulator getrennt und benutze den Begriff auch i.d.R. weiter so. Der Feldtrenner ist aber ja gar nicht unser Punkt, sondern der Datensatztrenner, und wenn man Daten von Dritten bekommt, dann tut man immer gut daran zu erfahren, wie die Daten nun genau strukturiert sind und wenn die Dokumentation etwas taugt und die Quelle verantwortungsvoll arbeitet, erfährt man solchen Unbill vorab, insbesondere wenn von 738.000 Datensätzen nur einer über 2 Zeilen reicht und das auch nur bei jeder 3. Datenlieferung.
|
|
skapi85
(Themenstarter)
Anmeldungsdatum: 19. August 2017
Beiträge: 48
|
seahawk1986 schrieb: Dann musst du den Ersetzungsbefehl anpassen:
Vielen Dank für das aktualisieren des Befehles! Für mich ist liefert der Befehl ein brauchbares Ergebnis! Danke!!
|
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11278
|
user_unknown schrieb: Ich habe CSV generell als überspezifische Bezeichnung für x-separated values kennengelernt, meist per Semikolon oder Tabulator getrennt und benutze den Begriff auch i.d.R. weiter so.
Dann reden wir wohl aneinander vorbei - ich habe da gedanklich zwei Kategorien, einmal die TSV-ähnlichen Formate (einfache Feldtrenner, kein Quoting), für die die CLI-Tools ausreichend sind (awk, cut, paste, sed, grep, gnuplot usw.) und dann die CSV-Dialekte (wie sie z.B. Excel genreriert), bei denen Feldgrenzen nicht am reinen Vorkommen eines bestimmten Zeichens identifizierbar sein müssen, weil Quoting im Spiel ist. Generell ist es sicherer einen Parser zu haben, der mit mehr als einem Subset von Features zurecht kommt (man kann TSV zuverlässig mit einem ausgewachsenen CSV-Parser verarbeiten, aber nicht umgekehrt).
und wenn man Daten von Dritten bekommt, dann tut man immer gut daran zu erfahren, wie die Daten nun genau strukturiert sind und wenn die Dokumentation etwas taugt und die Quelle verantwortungsvoll arbeitet, erfährt man solchen Unbill vorab, insbesondere wenn von 738.000 Datensätzen nur einer über 2 Zeilen reicht und das auch nur bei jeder 3. Datenlieferung.
Wenn man das Glück hat, dass da jemand eine Datequelle hat, für die sowas existiert - in der Praxis trifft man da leider oft auch auf ellenlange historisch gewachsenen Excel-Tabellen...
|
|
snafu1
Anmeldungsdatum: 5. September 2007
Beiträge: 2136
|
Anmerkungen zum Python-Code: - Diese Verrenkung mit sys.stdout erscheint mir unnötig kompliziert. Was wird dadurch gewonnen? - Um an die ersten Elemente des Streams zu kommen, kann man auch islice() benutzen, anstatt mitzuzählen. - Das Abschneiden der Buchstaben würde ich mit strip() machen bzw mit lstrip(), um es auf die linke Seite zu beschränken. Das versteht wie startswith() auch mehrere Buchstaben und kommt hier zum selben Ergebnis. - Im Falle einer einer einzelnen Datei würde ich den Dateinamen einfach als Konstante in den Code schreiben. Ist aber natürlich weniger flexibel, das stimmt schon. Ich lande dann in etwa hier:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | #!/usr/bin/env python3
import csv
from itertools import islice
import sys
CSV_FILE = "test.csv"
ROWS_SKIPPED = 7
STRIP_CHARS = "CFT"
with open(CSV_FILE, newline="") as instream:
reader = csv.reader(instream)
writer = csv.writer(sys.stdout)
writer.writerows(islice(reader, ROWS_SKIPPED))
for first, *rest in reader:
stripped = first.lstrip(STRIP_CHARS)
writer.writerow([stripped, *rest])
|
Ist es eigentlich gewollt, dass die Anführungszeichen bei der Ausgabe wegfallen?
|