Dieser Beitrag endet mit konkreten Beispielen, die kann man exakt so übernehmen. Dabei sind die Anführungszeichen in den Codefenstern richtig und wichtig!
Empfehlung: Komplizierte und lange Codezeilen immer in ein Script oder in eine Perl Sub schreiben! Mit Sonderzeichen, doppelten und einfachen Anführungszeichen kommt man schnell in sehr unübersichtliche, beinahe unlösbare Situationen. Immer die mögliche Blockade von FHEM im Hinterkopf haben!
Aufruf
Es gibt drei FHEM Kommandotypen, sie unterscheiden sich in der Verwendung der "Klammer"- FHEM Befehl - keine Klammer
- "Shell Befehl" - doppelte Anführungszeichen (blockiert FHEM nicht)
- {Perl Befehl} - geschweifte Klammer (kann FHEM blockieren)
Perl kennt in sich wieder drei Funktionsaufrufe für externe (nicht Perl) Programme.
- system() - liefert in FHEM immer "-1" zurück, Ausgaben landen im Logfile
- qx() - liefert die Ausgabe zurück, bei Aufruf in der FHEM Oberfläche landet diese im Browserfenster
- exec() - in FHEM nicht sinnvoll! Beendet FHEM und das aufgerufene Programm wird ausgeführt.
Kurze Fingerübung
Der Code ist für die FHEM Kommandozeile!"echo test"
Liefert nichts im Browserfenster, schreibt test ins Logfile.Innerhalb von Perlcode kann man das auch so verwenden:
{fhem("\"echo test\"")}
Perl system() liefert -1 im Browserfenster und schreibt test ins Logfile.{system("echo test")}
Variante von system() mit anderer Schreibweise: {system("programm","Arg1","Arg2")} und man bekommt das gleiche Ergebnis.{system("echo","test")}
Perl qx() liefert test im Browserfenster und schreibt nichts ins Logfile.{qx(echo test)}
Tipp: Diese Version verwende ich gern, um in der FHEM Oberfläche mal schnell etwas im System abzufragen. Man muss dazu nicht immer ins Terminal wechseln.Achtung: Die Pipeline (> |) der Standardausgabe funktioniert nur bei {system("echo system > test.txt")} und {qx(echo qx > test.txt)}, bei "echo kommando > test.txt" versagt sie aber!
Zwischenspiel
Wie bekomme ich Text in eine Datei? Am Besten mit der Perlfunktion FileWrite{FileWrite("test.txt","Inhalt")}
Das Ergebnis anschauen:{qx(cat test.txt)}
Beispiel mit Wertübergabe von FHEM
Dieses mehrzeilige Beispiel ist für die Raw Definition!Mit dem Code Block erzeugt man:
- ein Shellprogramm (echo $(($1*$2))) welches beide Übergabewerte multipliziert.
- ein Dummy mit dem Reading Wert,
- ein notify welches den Wert und state an das Shell Programm übergibt und die Rückgabe wieder den Wert schreibt.
{FileWrite("ext_Script.sh","echo \$((\$1*\$2))")}
define ext_dummy dummy
attr ext_dummy readingList Wert
attr ext_dummy room extern
attr ext_dummy setList 0 1 2 3 4 5 6 7 8 9
setreading ext_dummy Wert 1
define ext_notify notify ext_dummy:[0-9] {\
my $a = ReadingsVal($NAME,"Wert","2");;\
my $b = qx(bash ext_Script.sh $EVENT $a);;\
fhem("set $NAME Wert $b")\
}
attr ext_notify room extern
Diese Beispiel ist sicher etwas konstruiert, es zeigt aber verschiedenen Ebenen (FHEM, Perl, Shell) und auch die Übergabe von Werten über Variablen. Dabei gibt es einiges zu beachten:Sonderzeichen in Textketten werden innerhalb einfacher und doppelter Anführungszeichen je nach Ebene unterschiedlich behandelt! Bei doppelten Anführungszeichen gilt:
- Text der mit $ oder @ beginnt wird als Perl Variable/Array aufgelöst/ersetzt. Man kann/muss die Auflösung mit dem vorangestellten \ verhindern/schützen: "\$a , name\@domain.de".
- Ist das Anführungszeichen selbst nur Text: \".
- Den \ muss man selbst schützen: \\.
- Mit \ werden auch Steuerzeichen eingeleitet. z.B. \n ist ein Zeilenumbruch.
Beispiele zur Verwendung von Variablen
Zwei Perlzeilen und zwei Ebenen. Die Ausgabe ist gleich, die Variablen unterschiedlich.- $a ist eine Perl Variable und der Inhalt wird von Perl zur Shell übergeben.
- $a ist eine Shell Variable und wird erst in der Shell erzeugt. Es wird nichts übergeben.
{my $a = "test";;qx "echo $a"}
{qx 'a=test;;echo $a'}
Der Syntax von qx ist sehr variabel, alle Zeilen bewirken das Gleiche: den letzten Fehlercode der Shell ausgeben{qx "echo \$?"}
{qx 'echo $?'}
{qx /echo \$?/}
{qx (echo \$?)}
{`echo \$?`}
Mit einer Ausnahme könnte auch eine Perl Variablen übergeben werden (anstatt \$? $a).Achtung: Die Verwendung von qx("") führt zu einer Fehlermeldung, da ist offenbar ein String Terminator zu viel.
Damit es nicht zu leicht wird, kann man die String Terminatoren natürlich auch schachteln. Für die Auflösung von Perl Variablen ist dabei die äußere Klammer bestimmend!
Das erste Beispiel gibt Hallo Welt aus, das zweite nur Hallo. Die Variable $a wird im zweiten Fall nicht aufgelöst, sondern als Shell Variable interpretiert.
{my $a = "Welt";;qx "echo 'Hallo $a'"}
{my $a = "Welt";;qx 'a=FHEM;;echo "Hallo $a"'}
Ausnahmen? Verwirrung?Im folgenden Beispiel muss der Name der Variable im Shellscript auch geschützt werden.
Im Zweifelsfall hilft nur probieren!
{system('echo "wget --quiet - \"http://192.168.178.65/esp8266?max=Aussentemperatur+\$1+C\"" > ext_Script2.sh')}
Empfehlung: Befehle kapseln
Die Verwendung von Trennzeichen im String und die bisher genannten Interpretationen von Variablen Namen in den Umgebungen führt mich zu den unbedingten Empfehlung Befehle zu kapseln! Entweder in Stringvariablen in Perl oder in Scriptdateien für andere Sprachen. Es spart jede Menge Zeit und Nerven!Verkettung / concatenation
Perl kennt noch die String Verkettung mit Punkten.Im ersten Fall ist die Ausgabe identisch zur Version oben, im zweiten Fall wird der Inhalt der Variablen noch in Anführungszeichen ausgegeben:
{my $a = "test";;qx "echo ".$a}
{my $a = "test";;qx "echo ".'"'.$a.'"'}
Welche Methode man verwendet ist häufig Geschmacksache, manchmal aber auch vom Inhalt der Strings und dem gewünschten Ergebnis abhängig und damit das Mittel der Wahl.Achtung blockierend
Die Ausführung von externen Programmen mit Perl Aufrufen wird FHEM für die Laufzeit dieses Programmes unterbrechen/anhalten. Das kann unvorhersehbare Folgen haben.Der kurzer Perl Test erzeugt für 5 sec "Warten" und dann eine Ausschrift.
{sleep 5;; return "Das war eine Pause"}
{qx "echo Das war;;sleep 5;;echo eine Pause"}
Der zweite Test führt auf Shell Ebene ein sleep aus, beide echo Befehle werden aber erst am Ende über qx an den Browser zurück gegeben.Für einen weiteren Test erzeugen wir uns wieder ein Shell Script.
{FileWrite("ext_Pause.sh","sleep \$1\necho \$1 sec Pause")}
Dies erwartet eine Zahl und macht dann eine entsprechende Pause. Mit einem qx() Aufruf können wir das Script testen.{qx "echo Das war;;bash ext_Pause.sh 4"}
Nach meinen Informationen, lässt sich das Verhalten mit qx() nicht ändern. Die Befehle werden immer seriell ausgeführt, Perl wartet auf die Rückgabe!Der system() Aufruf hat andere Möglichkeiten. Bei ihm kann man mit dem & Zeichen am Ende (für die Shell typisch) die Ausführung im Hintergrund starten. Die Ausgabe im Script landet damit nach der Pause im Logfile von FHEM.
{system ("bash ext_Pause.sh 5 &")}
Aufräumen
Um die hier beim Testen erzeugten Geräte und Dateien wieder zu entfernen, kann man die beiden Zeilen einzeln in der Kommandozeile oder zusammen in der Raw Definition ausführen.delete ext_.*
{system ("rm ext_*.sh >/dev/null")}
Ganz zum Schluss
Mal noch als "Merker" der Tipp aus dem Forum. Den baue ich bei Gelegenheit mal noch aus.Code Block
Vielen Dank, Otto, hast mir geholfen!!!
AntwortenLöschenBitte weitermachen!
Gruß, FFHEM
Vielen Dank, damit hatte ich schon rumprobiert, hat aber nicht geklappt. Jetzt versteh ich das und probiere es nochmal.
AntwortenLöschenGruß
Hallo Otto,
AntwortenLöschenich versuche seit Tagen meinen Saugroboter über die FHEM-Befehlszeile zu starten, leider verzweifel ich langsam. Es gibt im Forum für meinen Typ eine Cloud-Lösung und sollte auch funktionieren. Im Terminal von meinem RPi4 kann ich mit z.b. sudo deebotozmo clean) Steuern, von FHEM aus versage ich. Folgendes sollte ja meinen Bot von der Befehlszeile aus starten:{system("sudo deebotozmo clean &")}
wurde mir zumindest so mitgeteilt, funktioniert aber leider nicht.
Ich hoffe Du kannst mir helfen.
VG Thomas
Folgendes (sudo: Kein TTY vorhanden und kein »askpass«-Programm angegeben) steht in der Logfile
AntwortenLöschenVG thomas
So sieht die /etc/sudoers aus
AntwortenLöschen#
# This file MUST be edited with the 'visudo' command as root.
#
# Please consider adding local content in /etc/sudoers.d/ instead of
# directly modifying this file.
#
# See the man page for details on how to write a sudoers file.
#
Defaults env_reset
Defaults mail_badpass
Defaults secure_path="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:$
# Host alias specification
# User alias specification
# Cmnd alias specification
# User privilege specification
root ALL=(ALL:ALL) ALL
# Allow members of group sudo to execute any command
%sudo ALL=(ALL:ALL) ALL
# See sudoers(5) for more information on "#include" directives:
#includedir /etc/sudoers.d
VG Thomas
Hallo Thomas,
Löschensorry aber Forumsbetrieb ist hier praktisch nicht möglich. Ich habe deine Beiträge im Forum gesehen. Ich werde den Fortschritt im Forum verfolgen. Die sudoers die Du zeigst ist gefährlich! Mach es so: https://heinz-otto.blogspot.com/2017/08/raspberry-ausschalten-mit-fhem.html
Gruß Otto
Das Setup in dem Beitrag und die Notwendigkeit von sudo versteh ich leider nicht. Aber ich kann das auch nicht nachvollziehen. Sorry da kann ich nicht weiterhelfen. Ich kann Dir nicht sagen für welches Programm Du sudo freischalten musst. Fakt ist Deine sudoers verhindert nicht, dass das passwort abgefragt wird. Mehr will ich aus Sicherheitsgründen nicht sagen.
LöschenGruß Otto