Liste in String

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Hi, ich bin hier neu und fange gerade erst mit Python an.

Ich verwende auf unserem Server Python 2.5 (Debian Lenny).
Es ist schon wichtig, dass das Script auch mit dieser Version funktioniert, da ich das später in unserem Forum für die Allgemeinheit anbieten will.
Die meisten GameServeradmins setzen Debian Lenny ein, welches zur Zeit python 2.5 beinhaltet.

Zur Zeit arbeite ich daran einen Watchdog, den ich in Shell-Script für GameServer geschrieben habe, in Python umzusetzen.
Um zum eigentlichen Problem zu kommen. Ich lese eine Liste aus, in der Admins eingetragen sind (Liste wird später aus einer Datei gelesen).
Mein eigentliches Problem ist, dass ich es so einfach wie möglich machen will.

Hier der Beispielcode, der so nicht funktionieren kann:

Code: Alles auswählen

admins = ['STEAM_0:0:xxxx','STEAM_0:0:aaaaa','STEAM_0:0:zzz','STEAM_0:0:yyy']
log = 'L 09/19/2010 - 14:51:20: "Player<87><STEAM_0:0:zzz><TERRORIST>" say "!rr"'

if admins in log and 'say' in log:
  print 'Admin gefunden'
Klar, es liegen hier zwei verschiedene Objekttypen vor. Dazu kommt noch das Problem, wenn es funktionieren würde, dass der String auch gefunden wird, wenn die SteamID im Log länger ist, aber den gesuchten String beinhaltet. Kommt bei einer SteamID in seltenen Fällen vor, aber ist halt möglich. So könnte dann ein nicht-Admin auch einen chatbefehl auf dem Gameserver ausführen, was in einem Liga-Match nicht gerade optimal ist.

Das Beispiel hab ich dann so gelöst, was aber sicherlich nicht optimal ist:

Code: Alles auswählen

admins = ['STEAM_0:0:xxxx','STEAM_0:0:aaaaa','STEAM_0:0:zzz','STEAM_0:0:yyy']
log = 'L 09/19/2010 - 14:51:20: "Player<87><STEAM_0:0:zzz><TERRORIST>" say "!rr"'

def admincheck(admins, string):
  for admin in admins:
    if '<' + admin + '>' in string:
      return True
    return False

if admincheck(admins, log) and 'say' in log:
  print 'Admin gefunden'
Sicherlich lässt sich mit einer einfachen Funktion am jeweils am Ende und am Anfang das Zeichchen '<' und '>' einsetzen.
Es ist schon wichtig, dass die Ausführungszeit so kurz wie möglich ist, da aus einem Screenlog Zeile für Zeile geparst wird (Shell-Script like: tail -f | while read line; do foo; done).
Das dann auch noch für jeden der fünf WarServer.

Ziel ist es bei bestimmten Chatbefehlen Befehle auf dem Server auszuführen (screen -S name -p server -X stuff $'zb_lo3\n'). Wer sich mal das komplette ShellScript ansehen will, kann es hier herunterladen: http://download.sa-hosting.eu/temp/watc ... ate.tar.gz

Das Script funktioniert soweit, aber wie man sicherlich erkennen kann, ist der Code sehr unleserlich und lässt sich schlecht erweitern. Viele verlieren schon den Überblick nach den ersten Zeilen :-D
Es soll nur der Watchdog in Python geschrieben werden. Den Rest lasse ich erstmal so, da es funktioniert.

Ich bin für jeden Hinweis dankbar. Mir würde schon ein Anstoß in die richtige Richtung ausreichen. Immerhin ist Python sehr gut dokumentiert, übersichtlich und die Community scheint sehr offen und freundlich zu sein. Genau mein Geschmack.
Zuletzt geändert von DeaD_EyE am Montag 20. September 2010, 17:20, insgesamt 1-mal geändert.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

So?

Code: Alles auswählen

if any(a in log for a in admins): print 'Admin gefunden'
the more they change the more they stay the same
karolus
User
Beiträge: 140
Registriert: Samstag 22. August 2009, 22:34

Hallo
Unter der Annahme das das Präfix 'STEAM_0:0:' für die Identifizierung eines Admins reicht und das Schlüsselwort 'say' immer irgendwo hinter dem Adminnamen auftaucht, reicht:

Code: Alles auswählen

import re
if re.search('((?:STEAM_0:0:.+?)>.*(?:say))', log): print 'Admin gefunden'
Gruß Karo
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Das reicht nicht, da das eine Steam-ID ist, ich glaub, die ist 9 stellig.

Ich hab da auch noch was mit einer regex:

Code: Alles auswählen

import re
log = 'L 09/19/2010 - 14:51:20: "Player<87><STEAM_0:0:zzz><TERRORIST>" say "!rr"'
admins = ['STEAM_0:0:xxxx','STEAM_0:0:aaaaa','STEAM_0:0:zzz','STEAM_0:0:yyy']
if re.search('<(STEAM_[^>]*)>', log).group(1) in admins: print 'admin gefunden'
the more they change the more they stay the same
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Vielen Dank erstmal für die Antworten. Jetzt muss ich selbst erstmal verstehen, was die einzelnen Funktionen bewirken. Mir persönlich gefällt Regex am besten, auch wenn es um einiges komplexer ist. Wenn ich Fortschritte gemacht habe, melde ich mich.

Die SteamID kann auch so aussehen: STEAM_0:1:xxxxx. Die Länge ist variabel. Wenn ich den gepostetn Syntax verstehe, ist die Länge laut dem Beispiel aber egal.

Was ich noch machen werde ist die Ausführungsgeschwindigkeit der einzelnen Beispiele zu testen. Ich denke mal, dass regex rechenintensiver ist (nur eine Annahme).
Wird aber sicherlich nur im ms-Bereich liegen.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
jbs
User
Beiträge: 953
Registriert: Mittwoch 24. Juni 2009, 13:13
Wohnort: Postdam

Bei der Handvoll von Daten fällt die Ausführungsgeschwindigkeit nicht ins Gewicht.

Bei hunderten Anfragen pro Sekunde könnte man sich Überlegungen zur Optimierung machen, aber nicht bei einer handvoll Admins, die ab und zu mal einen Befehl ausführen.
[url=http://wiki.python-forum.de/PEP%208%20%28%C3%9Cbersetzung%29]PEP 8[/url] - Quak!
[url=http://tutorial.pocoo.org/index.html]Tutorial in Deutsch[/url]
BlackJack

@DeaD_EyE: Ich denke Du machst Dir zu viele Gedanken über Geschwindigkeit. Wenn es aus einer Datei gelesen wird, ist höchstwahrscheinlich das Lesen von der Platte sowieso der Flaschenhals, egal wie "langsam" das Programm ist. Schreib das Programm so, dass es lesbar und wartbar ist. Wenn es dann *nachgewiesen* an realen Daten zu langsam ist, kann man immer noch messen wo am meisten Zeit verbraucht wird und dort gezielt ansetzen.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Die Datei wird beim Starten des Watchdogs einmal gelsen und in einer Liste gespeichert. Die Screenlogs werden auf eine RamDisk geschrieben und ab einer bestimmten Größe gelöscht, damit die Ramdisk nicht irgendwann voll ist (20MB). Das senkt auch die IO-Last der Platte. Nur bei einem Bestimmten Befehl wird die Liste neu geladen.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
jbs
User
Beiträge: 953
Registriert: Mittwoch 24. Juni 2009, 13:13
Wohnort: Postdam

@DeaD_EyE: Meine PN gar nicht bekommen?
[url=http://wiki.python-forum.de/PEP%208%20%28%C3%9Cbersetzung%29]PEP 8[/url] - Quak!
[url=http://tutorial.pocoo.org/index.html]Tutorial in Deutsch[/url]
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Doch, hab sie erst jetzt gelsen und schon geantwortet.
Bin gerade dabei mich mit den Regular Expressions zu beschäftigen.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

So, bin Dank jbs weitergekommen. Wir schreiben das Script jetzt zusammen. Durch einen guten Tip von ihm, bin ich auf 'The Regex Coach' gekommen. Er hat auch schonmal angefangen eine Klasse dafür zu schreiben.

Für den Log habe ich schonmal die Regex-Funktion genutzt:

Code: Alles auswählen

import re, shlex

log = 'L 09/19/2010 - 14:51:20: "Player<87><STEAM_0:0:123><TERRORIST>" say "!rr"'
re_command = re.compile(r'L \d{2}/\d{2}/\d{4} - \d{2}:\d{2}:\d{2}: "(.+?)<[1-9]+><(STEAM_\d:\d:\d+)><.+?>" say "!(.+?)"')

player, steamid, cmd = re_command.match(log).groups()
cmd = shlex.split(cmd)
Fälschlicherweise habe ich anfangs eine StemID mit Buchstaben gepostet. Das Beispiel war also falsch. Eine StemID enthält nur Zahlen. Ich hätte nicht gedacht, dass jemand direkt mit dem Beispiel arbeitet und dann den fertigen Code postet.
Den cmd splitte ich mit shlex auf. Es gibt z.B. einen Befehl, der ein Argument erwartet. Macht es Sinn das direkt wieder in cmd zu schreiben oder macht man sowas nicht um Fehler zu vermeiden? Auf jeden Fall funktioniert es so. Theoretisch könnte ich noch mit { '^' : 'Zeilenanfang', '$' : 'Zeilenende' } makieren. :-D

Wenn wir noch das Rcon-Protokoll mit einbauen (srcds.py) und den Log über UDP empfangen. Das würde den Daemon vom Server unabhängig machen. Da das UDP-Logging nativ vom Source Dedicated Server und auch vom Half-Life Dedicated Server unterstützt wird, lassen sich so die Logs auch auf einem anderen Host empfangen. So arbeitet z.B. HLstatsX:CE (Daemon -> perl, Webinterface -> php). Mich stört zur Zeit noch, dass ich nicht die Steuerzeichen kenne, die der Server sendet. Den Code von HLstatsX:CE finde ich jetzt auch nicht besonders übersichtlich. Es gibt zwar viele Beispiele, aber diese beziehen sich auf das Rcon-Protokoll und das läuft beim SRCDS über TCP. Durch die Regex-Funktion können die Steuerzeichen, dessen Sinn ich nicht verstehe, einfach ignoriert werden. Ich sehe in anderen Scripts, dass diese Steuerzeichen beim Rcon-Protokoll herausgefiltert werden bzw. für bestimmte Abfragen gesendet werden. Das bezieht sich aber nicht auf das Empfangen von den Logs über UDP.

Später hatte ich noch vor, eine Funktion einzubauen, die einen Admin über Jabber benachrichtigt, wenn der Server nicht mehr aktuell ist.
L 09/08/2010 - 22:19:21: Your server needs to be restarted in order to receive the latest update.
Da XMPP/Jabber OpenSource ist, wird es sicherlich auch schon fertige Libs/Klassen geben. Darauf werde ich mich aber erst konzentrieren, wenn ich in Python tiefer eingestiegen bin.

Eins habe ich noch nicht verstanden. Was genau macht die Funktion any(a in log for a in admins)?
Es geht zwar, aber ich habe die Arbeitsweise noch nicht verstanden. Gibt es dazu irgendwelche Hinweise in der Python-Doku?
Die Erklärung zu any() habe ich mir mit help(any) schon angesehen, nur leider verstehe ich nicht, was genau das hier macht: a in log for a in admins
Zuletzt geändert von DeaD_EyE am Montag 20. September 2010, 16:18, insgesamt 1-mal geändert.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
karolus
User
Beiträge: 140
Registriert: Samstag 22. August 2009, 22:34

Hallo
Das "re.compile"te Suchmuster kannst du selbst ".match"en lassen :

Code: Alles auswählen

player, steamid, cmd = re_command.match( log).groups()
Gruß Karo
Dav1d
User
Beiträge: 1437
Registriert: Donnerstag 30. Juli 2009, 12:03
Kontaktdaten:

Wenn irgend ein Wert "True" ist, in einem Objekt, über welches man iterieren kann, dann gibt any True zurück.

Code: Alles auswählen

>>> any([False, False, False])
False
>>> any([False, True, False])
True
>>> any('d')
True
>>> any(['', '', 0])
False
>>> any(['', 1])
True
the more they change the more they stay the same
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.
DeaD_EyE hat geschrieben:nur leider verstehe ich nicht, was genau das hier macht: a in log for a in admins
Das ist ein Generator-Ausdruck (Generator Expression). Suche mal in der Dokumentation danach. Vielleicht schaust du dir besser vorher noch List Comprehensions (auch in der Dokumentation) an. Beide Ansätze sind sich sehr ähnlich, LCs sind am Anfang vielleicht etwas leichter zu verstehen.

Sebastian
Das Leben ist wie ein Tennisball.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

karolus hat geschrieben:Hallo
Das "re.compile"te Suchmuster kannst du selbst ".match"en lassen :

Code: Alles auswählen

player, steamid, cmd = re_command.match( log).groups()
Gruß Karo
Hast Recht, hab ich mal in meinem Beitrag geändert, falls mal jemand anderes darauf stoßen sollte.
Ich muss mich noch an die objektorientierte Programmierung gewöhnen. Da ich vorher nur mit der prozedualen Spache Shell gearbeitet habe, ist das für mich eine Umgewöhnung.

@Dav1d: Danke, habs verstanden. Das hier:

Code: Alles auswählen

a in log for a in admins
ist wohl eine List Comprehension. Auf Englisch ist das nochmal genauer erklärt: http://docs.python.org/tutorial/datastr ... rehensions
jbs hat mich daruf hingewiesen.

Dann hab ich mal folgendes ausprobiert:

Code: Alles auswählen

>>> string = 'Ist ein Test'
>>> [ s in string for s in ['Ist','ein','Test']]
[True, True, True]
>>> ( s in string for s in ['Ist','ein','Test'])
<generator object at 0x7f11d82bc320>
>>> [ s in string for s in ['Ist','ein','Test']]
[True, True, True]
>>> [ wort in string for wort in ['Ist','ein','Test']]
[True, True, True]
>>> [ wort in string for wort in ['Ist','kein','Test']]
[True, False, True]
>>> any([ wort in string for wort in ['Ist','kein','Test']])
True
>>> any(wort in string for wort in ['Ist','kein','Test'])
True
>>> any(wort in string for wort in ['Soll','nicht','klappen'])
False
>>> list(s in string for s in ['Ist','ein','Test'])
[True, True, True]
>>> help(type)

>>> type(s in string for s in ['Ist','ein','Test'])
<type 'generator'>
>>> type([s in string for s in ['Ist','ein','Test']])
<type 'list'>
Ja, interessant....

Also einmal ist es ohne Klammern ein Generator und mit eckige Klammern eine Liste. Ich beschäftige mich erst sein ein paar Tagen mit Python, vieles lässt sich schon spielerisch herausfinden. Wen ich jetzt mit meinem Halbwissen richtig liege, entsteht der Generator aus einer Klasse. Die Liste an sich wird durch list(s in string for s in ['Ist','ein','Test']) erzeugt? Naja, komme jetzt ein bisschen durcheinander. War zu lange Wach. 31 Stunden sind zuviel. Irgendwie fesselt Python.

PS: Gerade erst fertig geworden und schon postet jemand direkt die Lösung :-D
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Ganz stimmt dein Halbwissen noch nicht ;-) Im Allgemeinen haben Generatoren erstmal nichts mit Klassen oder Listen zu tun. Mit ihnen können lediglich Werte "generiert" werden. Was du dann damit machst, wie zum Beispiel alle Werte in eine Liste zu packen, bleibt dir überlassen. Generatoren haben gegenüber LCs im Prinzip zwei Vorteile: Sie verbrauchen weniger Speicher, da immer nur ein Element gehalten werden muss und das angefragte Element wird erst dann berechnet, wenn es benötigt wird. So sparte man sich unter Umständen Berechnungen, welche nicht benötigt werden oder du kannst Generatoren erzeugen, die unendlich lange Folgen liefern. Von Nachteil ist natürlich, dass du nur auf das aktuelle Element zugreifen kannst. Alles davor wird vergessen, alles dahinter ist noch nicht bestimmt.

Sebastian
Das Leben ist wie ein Tennisball.
Benutzeravatar
DeaD_EyE
User
Beiträge: 1012
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Danke für die Erklärung.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Antworten