Forum: PC-Programmierung Hilfe mit sed Syntax


von Marcus (Gast)


Lesenswert?

Hi,

ich möchte mir gerne ein tool bauen, das regelmäßig die 
Internet-Bandbreite misst und mir die Ergebnisse in einer mysql 
Datanbank speichert

das tool speedtest-cli liefert mir bspw. folgenden String auf stdout:
1
1746,Vodafone DE,Frankfurt,2017-04-24T16:00:01.421032,292.1655728839447,102.308,120871529.47842085,11972051.983536232

(SERVER_ID,SPONSOR,SERVER_NAME,TIMESTAMP,DISTANCE,PING,DOWNLOAD,UPLOAD)
Ich bräuchte den String aber in einem anderen Format in einer neuen 
variable, um mit mysql ein insert machen zu können:

1
'1746','Vodafone DE','Frankfurt','2017-04-24T16:00:01.421032','292.1655728839447','102.308','120871529.47842085','11972051.983536232'

Sprich die  Kommas ersetzt durch single quotes und am Anfang und Ende 
der Zeile wieder je in Single Quote. Allerdings steige ich nicht durch 
die Dokumentation von sed.

echo $RESULT_DATA |  sed -e 's/,/'.''   funktioniert nicht und die 
quotes am Anfang und Ende fehlen auch ...

Danke für jede Hilfe

von Yalu X. (yalu) (Moderator)


Lesenswert?

sed -e "s/,/','/g;s/^\(.*\)$/'\1'/"

Innerhalb von '...' kannst du keine ' verwenden.

: Bearbeitet durch Moderator
von Marcus (Gast)


Lesenswert?

Hi Yalu,

das funktioniert! Danke dafür.

allerdings scheitere ich grad noch am insert aus der Variable heraus.
1
RESULT_DATA=$(python /root/speedtest.py  --server 1746 --csv)
2
SQL_RESULT_DATA=$(echo $RESULT_DATA | sed -e "s/,/','/g;s/^\(.*\)$/'\1'/")
3
mysql --host=10.8.0.18 --user speedtest --password=speedtest < echo "INSERT INTO `TEST_DATA` (`SERVER_ID`, `SPONSOR`, `SERVER_NAME`, `TIMESTAMP`, `DISTANCE`, `PING`, `DOWNLOAD`, `UPLOAD`) VALUES ( $SQL_RESULT_DATA )"

gibt mir
1
-bash: TEST_DATA: command not found
2
-bash: SERVER_ID: command not found
3
-bash: SPONSOR: command not found
4
-bash: SERVER_NAME: command not found
5
-bash: TIMESTAMP: command not found
6
-bash: DISTANCE: command not found
7
-bash: PING: command not found
8
-bash: DOWNLOAD: command not found
9
-bash: UPLOAD: command not found
10
-bash: echo: No such file or directory

wenn ich die Backticks mit \ außer kraft setze:
1
mysql --host=10.8.0.18 --user speedtest --password=speedtest < echo "INSERT INTO \`TEST_DATA\` (\`SERVER_ID\`, \`SPONSOR\`, \`SERVER_NAME\`, \`TIMESTAMP\`, \`DISTANCE\`, \`PING\`, \`DOWNLOAD\`, \`UPLOAD\`) VALUES ( $SQL_RESULT_DATA )"

bekomm ich
1
-bash: echo: No such file or directory


Jemand noch einen heißen tipp für mich das Bash mit echo nichts 
anzufangen weiß verwirrt mich etwas.

von The Unix Haters Handbook (Gast)


Lesenswert?

Welcome To the shells quoting hell

Python ist ja ohnehin schon im Einsatz: also das ganze in Python machen.
Das ist mein Rezept wenn das mit quoting in bash zu strub wird - 
denn in Python fällt das klarer aus.

von The Unix Haters Handbook (Gast)


Lesenswert?

> mysql --host=10.8.0.18 --user speedtest --password=speedtest <
> echo "INSERT INTO \`TEST_DATA\` (\`SERVER_ID\`, \`SPONSOR\`,

Hier ist es in der Tat falsch: "<" bedeutet "lese aus Datei, nicht "nimm 
die Ausgabe von Befehl"

Also:
1
echo WURSCHTELSTRINGMITQUOTES ¦ mysql....

denn "¦" bedeutet "leite Ausgabe von Befehllinks nach Eingabe von 
Befehlrechts"

von ... --- ... (Gast)


Lesenswert?

echo "INSERT INTO `TEST_DATA` (`SERVER_ID`, `SPONSOR`, `SERVER_NAME`, 
`TIMESTAMP`, `DISTANCE`, `PING`, `DOWNLOAD`, `UPLOAD`) VALUES ( 
$SQL_RESULT_DATA )" |
mysql --host=10.8.0.18 --user speedtest --password=speedtest


Deine Grundlagen sind noch mangelhaft.

von g457 (Gast)


Lesenswert?

> mysql [..] < echo [..]
             ^
Falsche Art Umleitung erwischt. echo davor und dann pipe ('|') danach. 
Oder wenns unbedingt sein muss in einen fifo schreiben und dadort per 
'<' rauslesen. Ooder per '<(echo [..])'.

HF

von Sheeva P. (sheevaplug)


Lesenswert?

Marcus schrieb:
> allerdings scheitere ich grad noch am insert aus der Variable heraus.
>
>
1
> RESULT_DATA=$(python /root/speedtest.py  --server 1746 --csv)
2
>

Wenn ich das richtig sehe, ist das Programm, das Du für den Speedtest 
benutzt, ein Python-Programm. Vermutlich ist es sehr viel einfacher und 
robuster, das Python-Programm zu hacken... oder -- wenn Du das originale 
Python-Skript nicht patchen magst -- die Ausgabe Deines Python-Programms 
wiederum mit einem anderen Python-Programm zu verarbeiten (das hier ist 
zwar für PostgreSQL, aber das Prinzip ist dasselbe):
1
#!/usr/bin/python
2
import os
3
import csv
4
import psycopg2
5
6
if __name__ == '__main__':
7
  conn = psycopg2.connect('dbname=example user=example password=1234')
8
  curs = conn.cursor()
9
  iph = os.popen('/root/speedtest.py --server 1746 --csv')
10
  reader = csv.reader(iph)
11
  for line in reader:
12
    curs.execute('INSERT INTO tabelle VALUES (?,?,?)', 
13
                 line[0], line[2], line[5])
14
  iph.close()
15
  conn.commit()
16
  conn.close()

Klar, man kann das auch in der Bash machen. Wenn man auf Schmerzen 
steht. ;-)

>
1
> mysql --host=10.8.0.18 --user speedtest --password=speedtest < echo 
2
>

"<" liest eine Datei ein, nicht die Ausgabe eines Kommandos. Daher 
versucht die Bash, eine Datei mit dem Namen "echo" einzulesen, und weil 
es die Datei vermutlich nicht gibt... Peng. Was Du machen willst, geht 
mit "<<<".

von Sheeva P. (sheevaplug)


Lesenswert?

... --- ... schrieb:
> "[...]
> VALUES (
> $SQL_RESULT_DATA )"

"[...] VALUES (${SQL_RESULT_DATA})"

Wenn schon, denn schon. ;-)

von Yalu X. (yalu) (Moderator)


Lesenswert?

Marcus schrieb:
> "INSERT INTO `TEST_DATA` (`SERVER_ID`, `SPONSOR`, `SERVER_NAME`, ...

Willst du hier wirklich die Backticks (`), oder sollen das Quotes (')
sein? Wenn ersteres, musst du sie mit einem Backslah escapen (\`), sonst
versucht die Shell, TEST_DATA, SERVER_ID usw. als Kommandos auszuführen,
was zu den entsprechenden Fehlermeldungen führt.

Auf den anderen Fehler (mit der Redirection) wurde ja schon mehrfach
hingewiesen.

von Marcus (Gast)


Lesenswert?

Ja, die Backticks sind so korrekt.

Abschließend das vielleicht nicht eleganteste aber funktionierende 
Resultat, falls mal jemand über diesen Thread stolpern sollte.

Script zur Ausführung durch cron
1
#/bin/bash
2
3
#run "speedtest.py --list" to get a list of servers nearby
4
#or use without --server to enable automatic selection.
5
#Make sure to replace your credentials
6
7
RESULT_DATA=$(python /yourpath/speedtest.py --server 1746 --csv)
8
SQL_RESULT_DATA=$(echo $RESULT_DATA | sed -e "s/,/','/g;s/^\(.*\)$/'\1'/")
9
echo "INSERT INTO \`TEST_DATA\` (\`SERVER_ID\`, \`SPONSOR\`, \`SERVER_NAME\`, \`TIMESTAMP\`, \`DISTANCE\`, \`PING\`, \`DOWNLOAD\`, \`UPLOAD\`) VALUES ( $SQL_RESULT_DATA );" | mysql --host=serverip --user=youruser --password=yourpassword --database=dbname


Spaltenbezeichnungen - Datentypen ggf anpassen.
1
+-------------+---------+------+-----+---------+----------------+
2
| Field       | Type    | Null | Key | Default | Extra          |
3
+-------------+---------+------+-----+---------+----------------+
4
| TEST_ID     | int(11) | NO   | PRI | NULL    | auto_increment |
5
| SERVER_ID   | int(11) | NO   |     | NULL    |                |
6
| SPONSOR     | text    | NO   |     | NULL    |                |
7
| SERVER_NAME | text    | NO   |     | NULL    |                |
8
| TIMESTAMP   | text    | NO   |     | NULL    |                |
9
| DISTANCE    | int(11) | NO   |     | NULL    |                |
10
| PING        | int(11) | NO   |     | NULL    |                |
11
| DOWNLOAD    | int(11) | NO   |     | NULL    |                |
12
| UPLOAD      | int(11) | NO   |     | NULL    |                |
13
+-------------+---------+------+-----+---------+----------------+

source von speedtest-cli : https://github.com/sivel/speedtest-cli


Danke an alle Mitwirkenden.

von Yalu X. (yalu) (Moderator)


Lesenswert?

Marcus schrieb:
> SQL_RESULT_DATA=$(echo $RESULT_DATA | sed -e "s/,/','/g;s/^\(.*\)$/'\1'/")

Hier solltest du $RESULT_DATA in Anführungszeichen schreiben, also so:

1
SQL_RESULT_DATA=$(echo "$RESULT_DATA" | sed -e "s/,/','/g;s/^\(.*\)$/'\1'/")

Sonst gehen ggf. mehrfache Leerzeichen im Originalstring, bspw. in

1
42,Blanks       Bros & Co,Space       Town,2017-04-24T16:00:01.421032,2.1,1.3,1.4,1.9

verloren. Der Fall wird zwar in der Praxis höchst selten auftreten, aber
sicher ist sicher :)

> echo "INSERT INTO \`TEST_DATA\` (\`SERVER_ID\`, …) VALUES ( $SQL_RESULT_DATA );"

Um die vielen Backticks nicht escapen zu müssen, kannst du auch den
ersten, konstanten Teil des Strings in Apostrophe setzen und nur den
Rest (mit der Variable) in Anführungszeichen:

1
echo 'INSERT INTO `TEST_DATA` (`SERVER_ID`, …)'" VALUES ( $SQL_RESULT_DATA );"

: Bearbeitet durch Moderator
von Moritz (Gast)


Lesenswert?

Wuerd da ja n kleines Perl-script nehmen, damit hast das in 5 Zeilen 
erledigt. Generell kann ich Perl empfehlen, wenn es darum geht, Text zu 
verarbeiten:
1
#!/usr/bin/env perl
2
use strict;
3
use warnings;
4
use DBI;
5
6
my $dbh = DBI->connect(
7
    "DBI:mysql:database=DBNAME;host=localhost;port=3306",
8
    "USER", "PASSWD",
9
);
10
11
open my $fh, '-|', '/root/speedtest.py', '--server 1746', '--csv' or die $!;
12
while( <$fh> ) {
13
  chomp;
14
  my $sth = $dbh->prepare( "INSERT INTO tabelle VALUES (?,?,?)" );
15
  my $cnt = 1;
16
  $sth->bind_param( $cnt++, $_ ) for split ",";
17
  $sth->execute();
18
}
19
close( $fh );
20
$dbh->disconnect;
Die Query muss natuerlich noch angepasst werden.

von Sheeva P. (sheevaplug)


Lesenswert?

Moritz schrieb:
> Wuerd da ja n kleines Perl-script nehmen, damit hast das in 5 Zeilen
> erledigt. Generell kann ich Perl empfehlen, wenn es darum geht, Text zu
> verarbeiten:

Dann braucht der Nutzer dann aber nicht nur eine Python-, sondern auch 
noch eine Perl-Installation. Auf einem ausgewachsenen Linux/UNIX 
sicherlich kein Problem, aber in beschränkten Umgebungen kann das 
durchaus ein Faktor sein.

Obendrein hat Dein Perl-Skript nicht 5, sondern mehr als dreimal so 
viele Zeilen Code -- nämlich 17, also drei mehr als das Python-Skript. 
Auch wenn ich beide Skripte eindampfe, kommt dasselbe Ergebnis (11:14) 
heraus -- und dabei habe ich das post-'for split ",";' noch nichteinmal 
ausgerollt, auch wenn ich derlei (legale!) Perl-Konstrukte für extrem 
unleserlich halte und sie in meiner Perl-Zeit daher immer tunlichst 
vermieden habe. ;-)

Zuletzt befürchte ich, daß Dein Perl-Skript leider ein wenig seltsam 
ist. Schließlich sind Prepared Statements ja gerade dazu da, einmal 
präpariert und dann mehrmals ausgeführt zu werden -- um bei 
gleichartigen Queries die Netzwerk- und Datenbanklast zu senken, indem 
der SQL-Code für das Prepared Statement nur einmal übertragen und durch 
den Query Planner und -Optimizer verarbeitet werden muß. Deswegen glaube 
ich, daß Dein Perl-Code zwar recht gut funktioniert, dieser hier aber 
besser wäre:
1
#!/usr/bin/env perl
2
use strict;
3
use warnings;
4
use DBI;
5
6
my $dbh = DBI->connect(
7
    "DBI:mysql:database=DBNAME;host=localhost;port=3306",
8
    "USER", "PASSWD",
9
);
10
11
open my $fh, '-|', '/root/speedtest.py', '--server 1746', '--csv' or die $!;
12
my $sth = $dbh->prepare( "INSERT INTO tabelle VALUES (?,?,?)" );
13
while( <$fh> ) {
14
  chomp;
15
  my @data = split ",";
16
  $sth->execute(@data);
17
}
18
$dbh->disconnect;
19
close( $fh );

Naja, ob man das in Perl oder in Python macht, ist sicherlich eine Frage 
der persönlichen Präferenz und Erfahrung. Wenn der "speedtest.py" jedoch 
ohnehin Python erfordert, gebührt ihm bei sonst gleichen Voraussetzungen 
wohl der Vorzug -- schon um externe Abhängigkeiten zu minimieren. ;-)

von Moritz (Gast)


Lesenswert?

Jo, die Zeile, in der die Query vorbereitet wird koennte man wohl noch 
aus der Schleife herausziehen. Die Schleife ansich ist je nachdem auch 
gar nicht noetig, das waere eher, wenn der speedtest.py eine Funktion 
zum kontinuierlichen messen hat, also immer wieder neue Zeilen 
ausspuckt.

Dass ich gar kein bind_param()-brauche, wusste ich noch gar nicht, das 
kuerzt das ganze natuerlich ab. Mein Vorschlag mit Perl bezieht sich 
halt insbesondere auf die schon vorhandene, sehr gute Regex-engine ab. 
Split kann ja nicht nur einzelne Zeichen matchen, sondern eben auch 
ganze regulaere Ausdruecke. Bei Python ist das quasi ueber 'import csv' 
geloest. In Perl (die richtige Regex voraus gesetzt) brauch ich keine 
extra module. Das wollte ich auch mit den 5 Zeilen ausdruecken, mehr als 
while( <fh> ), chomp, split, bind_param, execute braucht man nicht. 
Datenbankverbindung aufbauen und Prozess starten muss man ja sowohl mit 
Perl als auch mit Python.

von Sheeva P. (sheevaplug)


Lesenswert?

Moritz schrieb:
> Dass ich gar kein bind_param()-brauche, wusste ich noch gar nicht, das
> kuerzt das ganze natuerlich ab. Mein Vorschlag mit Perl bezieht sich
> halt insbesondere auf die schon vorhandene, sehr gute Regex-engine ab.

Eine ebensogute Regex-Engine haben heute viele Programmiersprachen, und 
natürlich auch Python, das sich dabei -- wie die meisten -- an den PCRE 
(Perl-compatible Regular Expressions) orientiert. Mit Ausnahme von Ruby 
verzichten die meisten allerdings aus guten Gründen darauf, dies in die 
Sprachsyntax zu integrieren -- schließlich ist das, zusammen mit seinen 
"automagischen" und manchmal sogar unsichtbaren Variablen wie $_, einer 
jener Punkte, die Perl den Ruf von "Write-Only-Code" eingebracht haben.

Deswegen habe ich auch in meiner Perl-Zeit auf solche Konstrukte lieber 
verzichtet und statt
1
while( <IN> ) { chomp; }

dann doch
1
while( $line = <IN> ) { chomp $line; }

geschrieben, das konnten auch Nicht-Perlianer halbwegs lesen. Für sowas 
gibt es ja immer noch den Obfuscated Perl Contest. ;-)

> Split kann ja nicht nur einzelne Zeichen matchen, sondern eben auch
> ganze regulaere Ausdruecke. Bei Python ist das quasi ueber 'import csv'
> geloest. In Perl (die richtige Regex voraus gesetzt) brauch ich keine
> extra module.

Das Parsen von CSVs ist wesentlich komplexer, als es auf den ersten 
Blick aussieht. In sehr einfachen Fällen wie hier kann man vielleicht 
mit einer Regex arbeiten, meistens schießt man sich dabei aber selbst in 
den Fuß.

In der Praxis will man deswegen einen zustandsbehafteten Parser, der mit 
Quoting, Escaping etc. korrekt umgehen kann, wie eben das csv-Modul von 
Python. Das gehört zur Standarddistribution und ist so in jeder normalen 
Python-Installation vorhanden.

Aber natürlich kann man auch in Python die Holzhammer-Methode benutzen 
und statt des csv-Moduls einfach mit
1
for line in iph:
2
    curs.execute('INSERT INTO tabelle VALUES (?,?,?)', 
3
                 line.strip().split(',')

oder etwas Ähnlichem arbeiten, kein Problem. Das String-Modul str mit 
den Funktionen strip() und split() ist ein Builtin, und wird daher in 
jedem Python-Interpreter beim Starten automatisch geladen. Aber, wie 
gesagt, eigentlich will man keinen Holzhammer, sondern einen Parser, wie 
in Perl mit Text::CSV ebenfalls hat -- dann aber als externes Modul.

> Das wollte ich auch mit den 5 Zeilen ausdruecken, mehr als
> while( <fh> ), chomp, split, bind_param, execute braucht man nicht.
> Datenbankverbindung aufbauen und Prozess starten muss man ja sowohl mit
> Perl als auch mit Python.

Das mal klar. ;-)

von Stromtuner (Gast)


Lesenswert?

echo j | format c:

War schon der Brüller ( so oder so ähnlich war das damals ) ?
StromTuner

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.