JoeMiller42
Anmeldungsdatum: 3. Februar 2016
Beiträge: Zähle...
|
Hallo zusammen, gegeben sei eine große Tabelle mit vielen Messwerten. Erzeugung erfolgt durch Fremd-Tool, die Formatierung liegt nicht in meiner Hand. Die Tabelle liegt als csv vor, Datentrenner ist das Semikolon. Für eine grafische Auswertung benötige ich hierzu die Spalten 2, 5, 6 und 15. Klarer Fall für gawk also.
cat messwerte.csv | gawk 'BEGIN {FS=";"}//{print $2";"$5";"$6";"$15}'
und man erhält
14:25:49.554; 47.648113; 9.485316; 12.2°C
14:25:54.773; 47.649829; 9.484125; 13.2°C
14:26:00.046; 47.651310; 9.482650; 13.0°C
14:26:05.312; 47.653037; 9.481636; 12.5°C
14:26:10.539; 47.654217; 9.480219; 11.8°C
14:26:15.828; 47.655945; 9.479249; 10.4°C
14:26:21.070; 47.657806; 9.480139; 9.8°C
14:26:26.328; 47.658745; 9.480906; 9.1°C
(zur Erläuterung der Spalten: Uhrzeit [in völlig sinnloser Genauigkeit], Koordinaten in lat/long sowie eine Temperatur). Zwei Dinge bekomme ich jetzt nicht hin, und ich bitte um eure Hilfe: erstens: die erste Spalte soll zur Korrelation mit anderen Tabellen in unix epoch time dargestellt werden. Hierzu schwebt mir vor, dass gawk gleich den Befehl
date --date='2019-02-25 14:26:21.070' '+%s' --> 1551101181
ausführt, wobei das Datum 2019-02-25 gerne manuell in das Skript eingefügt werden kann oder ggf. als Variable $Tag oder so. Die Uhrzeit steht in $2 und die Millisekunden sind, wie bereits oben angedeutet, nicht von Bedeutung. zweitens: in der letzten Spalte (Temperatur) soll das "°C" weg. Also irgendwas mit 's/°C//' in den gawk-Ausdruck hinein. Bei der Gelegenheit dürfen auch gerne gleich die überflüssigen Leerzeichen entfernt werden. Am Ende soll also die Tabelle wie folgt aussehen:
1551101181;47.657806;9.480139;9.8
...und ich habe am Ende was über gawk gelernt, was eigentlich noch viel wichtiger ist. Herzlichen Dank.
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12801
|
JoeMiller42 schrieb:
gegeben sei eine große Tabelle mit vielen Messwerten. Erzeugung erfolgt durch Fremd-Tool, die Formatierung liegt nicht in meiner Hand. Die Tabelle liegt als csv vor, Datentrenner ist das Semikolon. Für eine grafische Auswertung benötige ich hierzu die Spalten 2, 5, 6 und 15. Klarer Fall für gawk also.
cat messwerte.csv | gawk 'BEGIN {FS=";"}//{print $2";"$5";"$6";"$15}'
Glückwunsch! Du gewinnst einen "useless use of cat award". ☺ Das geht auch so | gawk -F \; '{print $2";"$5";"$6";"$15}' messwerte.csv
|
und man erhält
Kannst Du vielleicht noch ein paar Zeilen der Eingabe posten, damit man damit testen kann?
erstens: die erste Spalte soll zur Korrelation mit anderen Tabellen in unix epoch time dargestellt werden. Hierzu schwebt mir vor, dass gawk gleich den Befehl
date --date='2019-02-25 14:26:21.070' '+%s' --> 1551101181
ausführt, wobei das Datum 2019-02-25 gerne manuell in das Skript eingefügt werden kann oder ggf. als Variable $Tag oder so. Die Uhrzeit steht in $2 und die Millisekunden sind, wie bereits oben angedeutet, nicht von Bedeutung.
gawk kennt mktime() für das Parsen eines Zeitstempels. Das dürfte effizienter sein als date aufzurufen.
zweitens: in der letzten Spalte (Temperatur) soll das "°C" weg. Also irgendwas mit 's/°C//' in den gawk-Ausdruck hinein. Bei der Gelegenheit dürfen auch gerne gleich die überflüssigen Leerzeichen entfernt werden.
| $ gawk 'BEGIN {s="17 ° C ";print s;sub(/\s*°\s*C\s*$/,"",s);print s}'
17 ° C
17
|
|
JoeMiller42
(Themenstarter)
Anmeldungsdatum: 3. Februar 2016
Beiträge: 10
|
ich danke dir. Die Variante mit dem extern ausgeführten Date-Befehl hat den Charme, dass date das übergebene Datum fast immer selbst interpretieren kann. mktime() benötigt das Datum zwangsweise in der Form "YYYY MM DD HH MM SS[ DST]", was man auch erst so hinbauen müsste. Allerdings performt die externe date-Variante, wie erwartet, nicht so recht: Für 2,7 Mio Zeilen wird eine knappe Stunde benötigt. Und das liegt sicher nicht am überflüssigen cat ☺ Aber ich mag Katzen und Pipes. Der gesamte Befehl sieht jetzt so aus:
cat messwerte.csv | sed 's/ //g' | gawk 'BEGIN {FS=";"}//{command="date -d \"2019-02-26 "$2" \" +%s"; command | getline $1; close (command); print $1";"$5";"$6";"$15'
|
seahawk1986
Anmeldungsdatum: 27. Oktober 2006
Beiträge: 11176
Wohnort: München
|
JoeMiller42 schrieb: Allerdings performt die externe date-Variante, wie erwartet, nicht so recht: Für 2,7 Mio Zeilen wird eine knappe Stunde benötigt. Und das liegt sicher nicht am überflüssigen cat ☺
Wenn du eh einen Unix-Timestamp herausbekommen willst, kannst du auch einmalig den Timestamp für Mitternacht des Tages errechnen und dann nur den HH:MM:SS.ms String zerlegen und in Sekunden seit Tagesanfang umwandeln und zum Schluss beide Werte addieren. Unter der Annahme, dass die Zeitangabe der einzige Ort in der csv-Datei ist, an dem Doppelpunkte vor der letzten interessanten Spalte vorkommen können, könnte man z.B. mit alternativen Pfadtrennern in awk arbeiten:
$ wc -l larger_example.csv
2700000 larger_example.csv
$ head -n8 larger_example.csv
foo; 14:25:54.773; 3; 4; 47.649829; 9.484125; 7; 8; 9; 10; 11; 12; 13; 14; 13.2°C
foo; 14:25:49.554; 3; 4; 47.648113; 9.485316; 7; 8; 9; 10; 11; 12; 13; 14; 12.2°C
foo; 14:26:00.046; 3; 4; 47.651310; 9.482650; 7; 8; 9; 10; 11; 12; 13; 14; 13.0°C
foo; 14:26:05.312; 3; 4; 47.653037; 9.481636; 7; 8; 9; 10; 11; 12; 13; 14; 12.5°C
foo; 14:26:10.539; 3; 4; 47.654217; 9.480219; 7; 8; 9; 10; 11; 12; 13; 14; 11.8°C
foo; 14:26:15.828; 3; 4; 47.655945; 9.479249; 7; 8; 9; 10; 11; 12; 13; 14; 10.4°C
foo; 14:26:21.070; 3; 4; 47.657806; 9.480139; 7; 8; 9; 10; 11; 12; 13; 14; 9.8°C
foo; 14:26:26.328; 3; 4; 47.658745; 9.480906; 7; 8; 9; 10; 11; 12; 13; 14; 9.1°C
$ time awk -v base_ts=$(date -d "2019-02-26" +%s) -F ':|; ' 'BEGIN{OFS=";"}{gsub(/°C$/, "", $17);print int($2 * 3600 + $3 * 60 + $4 + base_ts),$7,$8,$17};' larger_example.csv > out_awk.csv
real 0m40,009s
user 0m38,165s
sys 0m0,556s
$ head -n8 out_awk.csv
1551187554;47.649829;9.484125;13.2
1551187549;47.648113;9.485316;12.2
1551187560;47.651310;9.482650;13.0
1551187565;47.653037;9.481636;12.5
1551187570;47.654217;9.480219;11.8
1551187575;47.655945;9.479249;10.4
1551187581;47.657806;9.480139;9.8
1551187586;47.658745;9.480906;9.1 Man könnte das ganze z.B. auch in C implementieren (aka Spaß mit Pointern):
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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 | #include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char * advance_semicolon(const char* chrptr)
{
// get the next semicolon
char *new_pos = strchr(chrptr, ';');
if (new_pos) {
// skip semicolon
++new_pos;
// advance to first non-space character
do { ++new_pos; } while (isspace(*new_pos));
}
return new_pos;
}
int parse_line(const char* line, FILE *fp_out, int64_t base_ts) {
int hours = 0;
int minutes = 0;
int seconds = 0;
int ms = 0;
double longitude = 0.0;
double latitude = 0.0;
float temp = 0.0;
char *new_pos = advance_semicolon(line);
if (!new_pos) {
fprintf(stderr, "no new semicolon found\n");
return 0;
}
// parse HH:MM:SS.ms
if (!sscanf(new_pos, "%d:%d:%d.%d", &hours, &minutes, &seconds, &ms)) {
fprintf(stderr, "could not parse time from: %s\n", new_pos);
return 0;
}
// advance to latitude
for (int i=0; i < 3; ++i) {
new_pos = advance_semicolon(new_pos);
if (!new_pos) {
fprintf(stderr, "no new semicolon found\n");
return 0;
}
}
// parse latitude
if (!sscanf(new_pos, "%lf", &latitude)) {
fprintf(stderr, "invalid formatted latitude\n");
return 0;
}
// advance to longitude
new_pos = advance_semicolon(new_pos);
if (!new_pos) {
fprintf(stderr, "no new semicolon found\n");
return 0;
}
// parse longitude
if (!sscanf(new_pos, "%lf", &longitude)) {
fprintf(stderr, "invalid formatted longitude\n");
return 0;
}
// advance to temperature
for (int i=0; i < 9; ++i) {
new_pos = advance_semicolon(new_pos);
if (!new_pos) {
fprintf(stderr, "no new semicolon found\n");
return 0;
}
}
// parse temperature
if (!sscanf(new_pos, "%f", &temp)) {
fprintf(stderr, "invalid formatted temperature\n");
return 0;
}
u_int32_t total_seconds = (hours * 3600) + (minutes * 60) + (int) seconds + base_ts;
fprintf(fp_out, "%d;%f;%f;%0.1f\n", total_seconds, latitude, longitude, temp);
return 1;
}
int main(int argc, char* argv[]) {
const char *base_ts_env = getenv("BASE_TS");
int64_t base_ts = 0;
if (base_ts_env) {
if (!sscanf(base_ts_env, "%ld", &base_ts)) {
fprintf(stderr, "could not convert environment variable BASE_TS\n");
}
} else {
fprintf(stderr, "missing environment variable BASE_TS!\n");
}
if (argc < 3) {
printf("usage: %s INPUT [INPUT ..] OUTPUT\n", argv[0]);
exit(0);
}
FILE* fp_out = fopen(argv[argc - 1], "w");
if (fp_out == NULL)
exit(EXIT_FAILURE);
for (int i=1; i < (argc - 1); i++) {
FILE* fp = fopen(argv[i], "r");
if (fp == NULL)
exit(EXIT_FAILURE);
char* line = NULL;
size_t len;
while ((getline(&line, &len, fp)) != -1) {
parse_line(line, fp_out, base_ts);
}
fclose(fp);
if (line)
free(line);
}
fclose(fp_out);
}
|
Das wäre dann noch mal spürbar schneller:
$ gcc -O3 -Wall -Wextra transform_data.c -o transform_data
$ ./transform_data
missing environment variable BASE_TS!
usage: ./transform_data INPUT [INPUT ..] OUTPUT
$ time BASE_TS=$(date -d "2019-02-26" +%s) ./transform_data larger_example.csv out.csv
real 0m22,364s
user 0m21,379s
sys 0m0,487s
$ head -n8 out.csv
1551187554;47.649829;9.484125;13.2
1551187549;47.648113;9.485316;12.2
1551187560;47.651310;9.482650;13.0
1551187565;47.653037;9.481636;12.5
1551187570;47.654217;9.480219;11.8
1551187575;47.655945;9.479249;10.4
1551187581;47.657806;9.480139;9.8
1551187586;47.658745;9.480906;9.1
|
rklm
Projektleitung
Anmeldungsdatum: 16. Oktober 2011
Beiträge: 12801
|
JoeMiller42 schrieb:
Die Variante mit dem extern ausgeführten Date-Befehl hat den Charme, dass date das übergebene Datum fast immer selbst interpretieren kann. mktime() benötigt das Datum zwangsweise in der Form "YYYY MM DD HH MM SS[ DST]", was man auch erst so hinbauen müsste.
Ist nicht so schwer: | $ gawk -v "date=$(date '+%Y %m %d')" 'BEGIN {s=date " " "14 25 54.773";print s;t=mktime(s);print strftime("%Y-%m-%d %H-%M-%S", t)}'
2019 03 13 14 25 54.773
2019-03-13 14-25-54
$ gawk -v "date=$(date '+%Y %m %d')" 'BEGIN {field="14:25:54.773"; gsub(/[^0-9]+/," ",field);print field;s=date " " field;print s;t=mktime(s);print strftime("%Y-%m-%d %H-%M-%S", t)}'
14 25 54 773
2019 03 13 14 25 54 773
2019-03-13 14-25-54
$ gawk -v "date=$(date '+%Y %m %d')" 'BEGIN {field="14:25:54.773"; gsub(/[^0-9.]+/," ",field);print field;s=date " " field;print s;t=mktime(s);print strftime("%Y-%m-%d %H-%M-%S", t)}'
14 25 54.773
2019 03 13 14 25 54.773
2019-03-13 14-25-54
|
field soll dabei der Inhalt des Zeitfeldes in der CSV-Datei sein. date muss man ja sowieso von außen zufüttern, weil es nicht in der Datei steht.
Musst Du jetzt nur für Deine Bedürfnisse um- oder einbauen. ☺
|