staging.inyokaproject.org

Sed CSV Datei Suchen & Ersetzen

Status: Gelöst | Ubuntu-Version: Kein Ubuntu
Antworten |

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...

1
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.:

1
2
"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:

sed '8,$s/^"[CFT]/"/' 

Bei csv mit Quoting würde ich tendenziell eher zu einem richtigen Parser greifen (Python hat z.B. ein brauchbares csv Modul).

user_unknown

Avatar von user_unknown

Anmeldungsdatum:
10. August 2005

Beiträge: 17630

seahawk1986 schrieb:

Dann musst du den Ersetzungsbefehl anpassen:

sed '8,$s/^"[CFT]/"/' 

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
2
3
4
5
6
7
8
9
"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

Avatar von 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
2
3
4
5
6
7
8
9
"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

Avatar von 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

Avatar von 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:

sed '8,$s/^"[CFT]/"/' 

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

Avatar von 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?

Antworten |