Wie schreibe ich Eggdrop Scripte, die sich nicht an Sonderzeichen verschlucken

von "Peterre",
Übersetzt von "CyBex"
Original: http://www.peterre.info/characters.html


Eggdrop Scripte die sich verschlucken
Viele Eggdrop Scripte verschlucken sich an Nicks, Usernamen oder Texten, die Sonderzeichen enthalten, die eine besondere Bedeutung in Tcl haben wie zB [, ], {, }, ", \, und $. Aus diesem Grund verbieten einige Channel Nicks mit solchen Zeichen.

Dennoch kann das Problem vollständig behoben werden, indem man korrekten Tcl Code schreibt. Es gibt zwei goldene Regeln, die beachtet werden müssen.

Die erste goldene Regel von Tcl für Eggdrop Scripte
Baue alle splits und joins ein, die notwendig sind, um zwischen Listen und Strings umzuwandeln.

Es ist am besten, Listen und Strings einzeln zu betrachten; als ob es zwei verschiedene Datentypen wären und dann alle splits und joins einzubauen, die notwendig sind, um zwischen ihnen umzuwandeln. Wenn wir das tun, ergreift der Interpreter die notwendigen Maßnahmen um mit allen Sonderzeichen klarzukommen. Das ist für uns von großem Vorteil, da wir nicht verstehen müssen, wie Listen intern dargestellt werden (ein Thema, das nicht gerade einfach zu verstehen ist, wenn sie Sonderzeichen enthalten sind) - stattdessen, lassen wir einfach diese Arbeit vom Interpreter erledigen.

Als Beispiel, drei Zeilen aus einem Eggdrop Script:

bind dcc o tell do_dcc_tell
proc do_dcc_tell { hand idx arg } {
set arg [lrange $arg 0 0]

Wenn die Proc aufgerufen wird (partyline: .tell <nick> <text>), ist das erste Wort in arg ein Nickname. Das Script verschluckt sich bei Nicks, die [ oder { enthalten. Es ist die dritte Zeile, die das Problem verursacht.

Das Script hätte auch lauten können:
set arg [lindex $arg 0]
Aber das führt auch zu einem Problem, wenn der Nick Tcl Sonderzeichen enthält.

Um zu verstehen, warum der oben genannte Code falsch ist, muss man verstehen, daß alle Prozeduren, die auf einen bind reagieren, erstmal nur Strings besitzen (es sei denn du verwendest das Schlüsselwort args, siehe dazu weiter unten). Also ist auch arg ein String. lrange und lindex jedoch, sollte nur auf Listen angewendet werden. Folglich müssen wir den String in eine Liste umwandeln, bevor wir lrange oder lindex benutzen. Das geht mit split. Um es also zu korrigieren, ändern wir die erste die Zeile in:
set arg [lrange [split $arg] 0 0]

Jedoch ist das immer noch falsch. Der Rest der Proc erwartet, daß arg ein String ist, und keine Liste. lrange gibt jedoch eine Liste zurück (in diesem Fall mit einem Element). Folglich müssen wir join benutzen, um die Liste in einen String umzuwandeln. Unser abschließende korrigierte Version ist also:
set arg [join [lrange [split $arg] 0 0]]

Alternativ (und weil es ordentlicher aussieht), können wir die andere Version, d.H:
set arg [lindex $arg 0]
auf die gleiche Weise ändern:
set arg [lindex [split $arg] 0]

In diesem Fall brauchen wir kein join, da lindex einen String zurückgibt.

Merke:

Ein angemessener Zeitpunkt um zu erwähnen, daß, wenn das letzte Argument von do_dcc_tell anstelle von arg nun args heissen würde, die Situation eine ganz andere wäre. Wenn eine Tcl Proc als letztes Argument den speziellen Namen args hat, benimmt sich dieses Argument völlig anders als alle anderen Bezeichnungen.

Ist hier ein anderes Beispiel für ein Eggdrop Script. Dieses funktioniert ohne Probleme mit Sonderzeichen.

bind pub B|B !orderfor pub_orderfor
proc pub_orderfor {nick uhost hand chan rest} {
  set rest [split $rest]
  if {[llength $rest] < 2} {
    puthelp "NOTICE $nick :Syntax: !orderfor <nick to order something for> <what to order>"
    return 0
  }
  puthelp "PRIVMSG $chan :\001ACTION sets [join [lrange $rest 1 end]] in front of [lindex $rest 0], compliments of $nick.\001"
  return 0
}

Die ursprüngliche Version (die ich aus einer Scriptdatenbank habe), enthielt nicht
set rest [split $rest]
aber Sie enthielt
set cmd [string tolower [lindex $rest 0]]
Hier ist klar, daß wir einen Fehler machen, indem wir lindex auf einen String anwenden.

Das ursprüngliche Script würde manchmal falsche Antworten geben, wenn die Eingabe Sonderzeichen enthielt.

Die zweite goldene Regel von Tcl für Eggdrop Scripte
Wenn das Script einen Befehl enthält, der ausgeführt werden soll, wenn ein Timer abläuft, muss er in der korrekten Form sein. (Also eine Liste!)

Der list Befehl liefert eine bequeme Weise um einen Befehl in die korrekte Form zu bringen. Er setzt Gegenschrägstriche (Backslashes) und Klammern ein, um mit allen Tcl Sonderzeichen fertig zu werden, die möglich sind.

Hier ist eine Prozedur, bei der die zweite goldene Richtlinie verletzt wird. Es begrüßt automatisch jeden, der den Channel betritt, außer wenn sie in den letzten 3 Minuten bereits gegrüßt wurden.

bind join - * do_jn_msg
proc do_jn_msg {nick uhost hand chan} {
  global jn_msg_done
  if {[isbotnick $nick]} {return 0}
  if {[info exists jn_msg_done($nick:$chan)]} {return 0}
  set jn_msg_done($nick:$chan) 1
  timer 3 "unset jn_msg_done($nick:$chan)"
  puthelp "NOTICE $nick :Welcome to $chan"
  return 0
}

Nehmen Sie an, daß der nick den Wert abc und chan den Wert #room hat. Die Anführungszeichen sorgen dafür, daß für $nick und $chan der Wert der Variable eingesetzt wird, so daß der Befehl der an den Timer übergeben wird so aussieht:
unset jn_msg_done(abc:#room)

Wenn der Timer abläuft, wird der unset ohne Probleme abgearbeitet.

Aber nehmen Sie nun an, daß wir statt dem Wert abc nun im nick der Wert a[b]c steht. Die Anführungszeichen sorgen wieder dafür das die Werte für $nick and $chan eingesetzt werden, das der Befehl im Timer wie folgt wird
unset jn_msg_done(a[b]c:#room)

Wenn der Timer abläuft, versucht der Tcl Interpreter, diesen Befehl auszuführen. Der erste Schritt der Ausführung ist, alle Variablen (zB: $abc), Befehle (zB: [abc]) und alle Zeichenkodierungen (zB: \n) durch ihren (Rückgabe-)Wert zu ersetzen.

Folglich versucht der Interpreter, [b] als Befehl auszuführen und mit der Rückgabe zu ersetzen. Dies hat den Effekt, daß er eine Fehlermeldung ausgibt und abbricht, da es keinen Befehl b gibt. Wenn der Nick a[die]c wäre, würde der Befehl die ausgeführt und den Bot beenden.

Um das Script zu bereinigen, ersetzen wir den Timer-Befehl durch den hier:

timer 3 [list unset jn_msg_done($nick:$chan)]

Wenn irgendwelche Tcl Sonderzeichen dabei sind, fügt list Gegenschrägstriche (Backslashes) und/oder Klammern hinzu, um einen Befehl die korrekte Form zu geben. Wir brauchen nicht zu wissen, wie diese Form aussieht. Wir müssen uns nichtmal Gedanken darum machen. Der unset Befehl, jetzt in der korrekten Form, wird zum Timer geführt und ohne Problem ausgeführt, wenn der Timer abläuft.

Tatsächlich im Fall von a[b]c, setzt der list Befehl einfach einige Extraklammern ein. Wenn der Nick a[b]c{ wäre, würde er einige Backslash Zeichen einsetzen. Aber wir brauchen nichts davon zu wissen. Wir verwenden einfach den list Befehl und lassen den Interpreter sich um die Details kümmern.

Merke:

Mehrere Scripte, die ich gesehen habe, sind ähnlich wie die oben genannten Beispiele. Manchmal ist es $uhost statt des $nick. Aber der Userhost kann auch Tcl Sonderzeichen enthalten, so daß die gleichen Probleme entstehen und auf die gleiche Weise behoben werden können wie oben beschrieben, mit dem list Befehlt.

Zugegeben, in dem obigen Beispiel wäre es sinnvoller, den Userhost $uhost zu verwenden, um eine Doppelbegrüßung zu verhindern, anstatt $nick. $nick habe ich im Beispiel verwendet, weil es für den Leser so einfacher ist, mit dem Script herum zu experimentieren, wenn er es in eine Tcl Quelldatei eingefügt hat.

Andere Möglichkeiten um mit dem Probleme umzugehen, die mit Tcl Sonderzeichen auftreten
Es gibt andere Möglichkeiten, die manchmal verwendet werden, um die Probleme zu vermeiden, die mit Tcl Sonderzeichen auftreten können.

Z.B. filtern einige Scripte ihre Eingabe mit etwas wie dem folgendem:

proc filt {data} {
  regsub -all -- \\\\ $data \\\\\\\\ data
  regsub -all -- \\\[ $data \\\\\[ data
  regsub -all -- \\\] $data \\\\\] data
  regsub -all -- \\\} $data \\\\\} data
  regsub -all -- \\\{ $data \\\\\{ data
  regsub -all -- \\\" $data \\\\\" data
  return $data
}

Solch ein Filter kann das Tcl Sonderzeichenproblem in der Tat manchmal verbessern in einem schlecht geschriebenen Eggdrop Script. Aber ob es wirklich hilft, hängt von den Details des Scripts ab. Es kann aber genauso gut passieren, daß es garnicht hilft, oder das Problem nurnoch verschlimmert. Es löst auf keinen Fall alle Probleme mit Sonderzeichen! Das tut nur die Beachtung der goldenen Regeln. Solch ein Script kann sogar Probleme entstehen lassen, wo vorher garkeine waren.

Wenn ein Script sich bei Tcl Sonderzeichen verschluckt, dann ist es weit besser, den Code so zu verbessern, daß die zwei goldenen Regeln befolgt werden, als eine Flickschusterei wie den oben genannte Filter anzuwenden, die nicht garantieren, unter allen Umständen zu richtig arbeiten und weitere Probleme sogar hervorrufen könnten.

Eine wichtige Anmerkung zu args
Das Benutzen von args als letztes Argument in einer Tcl Proc erlaubt die Benutzung von unbestimmter Anzahl an Parametern. Aber es ist auch eine mögliche Ursache für Verwirrung, wenn die Proc von einem Eggdrop Bind Befehl ausgeführt wird.

In einem Script, den ich downloadete, sah ich Code, der dem folgenden ähnlich ist:

bind dcc - m2f dcc_calc_m2f
proc dcc_calc_m2f {hand idx args} {
  if {[llength $args] == "1"} {

Der Autor hatte zweifellos gedacht, wenn ein Benutzer schreibt
.m2f aaa bbb ccc
dann würde der Wert von args eine Liste von drei Elementen, aaa, bbb und ccc sein, und folglich die Rückgabe von [llength $args] eine 3 sein.

Tatsächlich ist aber args eine Liste mit einem Element, und zwar der Zeichenkette aaa bbb ccc. Egal was der Benutzer schreibt, der Wert von [llength $args] wird immer 1 sein. (Sollte zumindest theoretisch. In Wirklichkeit funktioniert es wie 'vorgestellt', aber verursacht viele Sonderzeichenprobleme).

Ähnlich ist der Wert von [lindex $args 0] nicht die Zeichenkette aaa, sondern der String aaa bbb ccc.

Noch ein Beispiel für eine veranschaulichung zur korrekten Verwendung von args:

proc hallo {args} {
  set nick [lindex $args 0]
  set uhost [lindex $args 1]
  set handle [lindex $args 2]

Die Eggdrop pub und msg binds benehmen sich in der gleichen Weise.

Die oben genannten Eigenschaften des Befehls 'bind' der Eggdrops sind überprüft durch Ausprobieren mit einem Eggdrop Version 1.6.17.

Alle Rechte am Original sind bei Peterre
Original von Peterre, zu finden auf: http://www.peterre.info/characters.html
Übersetzt von 'CyBex' mit Unterstützung von thommey und #Tcl @ QuakeNet
mailto: sonderzeichen @ cybex . has . nolife . org