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:
(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:
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
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.
> 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"
> 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
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):
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 "<<<".
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.
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.
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
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 );"
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:
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:
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. ;-)
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.
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. ;-)