[gelöst] Externe Linux-Kommandos starten

Für Fragen von Einsteigern und Programmieranfängern...
Antworten
AndreasMR
Beiträge: 98
Registriert: Di 4. Aug 2015, 15:29
OS, Lazarus, FPC: Linux, Raspbian, Windows
CPU-Target: 64/32 Bit

[gelöst] Externe Linux-Kommandos starten

Beitrag von AndreasMR »

Hallo zusammen,

momentan entwickle ich einen Socket-Client in Lazarus, der mit einem externen Socket-Server kommuniziert. In der Regel werden Messwerte von Sensoren übertragen, die von einem anderen Programm bereitgestellt werden.

Dies alles funktioniert soweit.

Was nicht funktioniert, ist das Aufrufen externer Programme wie z.B. Linux-Programme.

Momentan mache ich dies mit:

Code: Alles auswählen

 
procedure TFormButtonClick(Sender: TObject);
var AProcess : TProcess;
    AStringList : TStringList;
    i : integer;
 
begin
    AProcess := TProcess.create(nil)
    AStringList := TStringList-Create;
 
    AProcess.Options := AProcess.Options + [poNoConsole, poWaitOnExit, poUsePipes];
    AProcess.CommandLine := Edit2.Text;
 
    AProcess.Execute;
 
    AStringList.LoadFromStream(AProcess.Output);
    Memo1.Append(AStringList.Text);
 
    AStringList.Free;
    AProcess.Free;
end;
 
Dieser Code soll ein externes Linux-Kommando starten und dessen Ausgabe in ein Memo-Feld ausgeben.

Das für mich Verblüffende besteht nun darin - und deswegen wende ich mich ans Forum - dass dies bei manchen Linux-Kommandos funkioniert und bei anderen so rein gar nichts passiert.

Z.B. wird die Ausgabe von pwd (eine Zeile) oder dir (wenige Zeilen) ausgegeben - beim Kommando df (ebenfalls wenige Zeilen Ausgabe) passiert gar nichts.

Als Kontrast dazu habe ich mal dmesg aufrufen lassen. Säuberlich werden Hunderte von Zeilen ausgegeben.

Der Effekt hat also nichts mit der Menge der Ausgabe an sich zu tun.

Hat jemand eine Idee, wie sich Linux-Kommandos einheitlich starten und die Ergebnisse nutzen lassen?

Falls von Bedeutung:
Hardware: Raspberry Pi
Betriebssystem: Raspbian Wheezy (4.1.7 #817)
Lazarus: 0.9.30.4-6
FPC: 2.6.0

Alternativen zum gewählten Socket-Konzept kommen als Lösung nicht in Frage.

Beste Grüße

Andreas
Zuletzt geändert von AndreasMR am So 5. Jun 2016, 00:50, insgesamt 2-mal geändert.
Ubuntu 14.04 LTS / Raspbian / Windows: Lazarus ab 0.9 bis 3.0

volker
Beiträge: 25
Registriert: Mi 14. Nov 2012, 14:36
OS, Lazarus, FPC: Linux Kernel 6.6 (L 3.0 FPC 3.2.2)
CPU-Target: x86_64 Linux gtk2

Re: Externe Linux-Kommandos starten

Beitrag von volker »

Hallo Andreas,

also meiner Erfahrung nach funktioniert der Aufruf von externen (Linux-) Programmen sehr gut,
Schau aber bitte mal im Tutorial (Wiki) nach. poUsePipes ist zwar gut, kann aber auch deadlocks verursachen bei mehr als 2048 (?) Zeichen.
http://wiki.freepascal.org/Executing_Ex ... rograms/de
Abschnitt: "Einlesen eines großen Outputs". Vielleicht sollte man das immer so machen (zur Sicherheit), den Output als MemoryStream einlesen.
Kannst du sicher sein, dass stets nur eine kleine Zahl von Zeichen als Output zurückkommt?
Man möge noch beachten: Das externe Programm MUSS mit vollem Pfad angegeben werden, path scheint nicht zu funktionieren, TProcess ist KEINE shell, die in der Benutzer-Umgebung läuft.
Hoffe das hilft weiter, viele Grüße

marcov
Beiträge: 1102
Registriert: Di 5. Aug 2008, 09:37
OS, Lazarus, FPC: Windows ,Linux,FreeBSD,Dos (L trunk FPC trunk)
CPU-Target: 32/64,PPC(+64), ARM
Wohnort: Eindhoven (Niederlande)

Re: Externe Linux-Kommandos starten

Beitrag von marcov »

Es ist einfacher runcommand zu nutzen. Siehe http://www.freepascal.org/docs-html/fcl ... mmand.html

Das ist ein Wrapper fuer TProcess mit Großen Output. Der Deutscher Tprocess Wiki ist da etwas veraltet.

AndreasMR
Beiträge: 98
Registriert: Di 4. Aug 2015, 15:29
OS, Lazarus, FPC: Linux, Raspbian, Windows
CPU-Target: 64/32 Bit

Re: Externe Linux-Kommandos starten

Beitrag von AndreasMR »

Hallo Volker,


Die Zeilen, die ich im Beitrag #1 gepostet habe, ermöglichen beispielsweise die Ausgabe von dmesg (gerade überprüft): 28468 Zeichen.

Die Ausgabe von df wäre nur 457 Bytes groß - funktioniert mit den obigen Zeilen nicht.

Vielen Dank für den Link - den kannte ich schon :) - wie man unschwer an meinem Code erkennen kann... :)

Bei den Linux-Kommandos handelt es sich um einfache Kommandos, die man zur Abfrage des Systemstatus in einem Terminal eingeben würde.

Aber es wird noch verrückter:
uname funktioniert nicht - uname -a liefert aber ein Ergebnis
df und df -h liefern kein Ergebnis.
ls liefert ein Ergebnis - ls -al aber nichts...
/bin/uname liefert kein Ergebnis - /bin/uname -a aber schon...

Ich vermag da weder ein Sytem zu erkennen noch eine Logik, die ich softwaremäßig abbilden könnte.

Beste Grüße

Andreas
Ubuntu 14.04 LTS / Raspbian / Windows: Lazarus ab 0.9 bis 3.0

marcov
Beiträge: 1102
Registriert: Di 5. Aug 2008, 09:37
OS, Lazarus, FPC: Windows ,Linux,FreeBSD,Dos (L trunk FPC trunk)
CPU-Target: 32/64,PPC(+64), ARM
Wohnort: Eindhoven (Niederlande)

Re: Externe Linux-Kommandos starten

Beitrag von marcov »

AndreasMR hat geschrieben: Aber es wird noch verrückter:
uname funktioniert nicht - uname -a liefert aber ein Ergebnis
df und df -h liefern kein Ergebnis.
ls liefert ein Ergebnis - ls -al aber nichts...
/bin/uname liefert kein Ergebnis - /bin/uname -a aber schon...

Ich vermag da weder ein Sytem zu erkennen noch eine Logik, die ich softwaremäßig abbilden könnte.

Was man auf der Konsole sieht sind zwei Streams, StdOut und Stderr. Der Standard Kode fangt nur stdout.

Versuch mal poStderrToOutPut and die Optionen hinzufügen.

AndreasMR
Beiträge: 98
Registriert: Di 4. Aug 2015, 15:29
OS, Lazarus, FPC: Linux, Raspbian, Windows
CPU-Target: 64/32 Bit

Re: Externe Linux-Kommandos starten

Beitrag von AndreasMR »

Hallo Marcov,

in der Zwischenzeit habe ich mit die Dokumentation zu TProcess und dort die Infos zu
- Execute
- Commandline
- Executable
- Parameters
durchgelesen.

Mein Code hat sich jetzt ein wenig geändert:

Code: Alles auswählen

 
...
AProcess.Options := AProcess.Options + [poNoConsole, poWaitOnExit, poUsePipes, poStderrToOutPut];
AProcess.Executable := '/bin/ls'; // hardcodiert
AProcess.Parameters.Add('-al');
AProcess.execute;
...
 
Hm, keine Ausgabe... setze ich dort statt ls uname und statt -al -a ein, dann folgt wieder die Ausgabe wie bei uname -a. Das heißt, diese Funktionen und Parameter sind sinnvoll gesetzt.

Beste Grüße

Andreas
Ubuntu 14.04 LTS / Raspbian / Windows: Lazarus ab 0.9 bis 3.0

marcov
Beiträge: 1102
Registriert: Di 5. Aug 2008, 09:37
OS, Lazarus, FPC: Windows ,Linux,FreeBSD,Dos (L trunk FPC trunk)
CPU-Target: 32/64,PPC(+64), ARM
Wohnort: Eindhoven (Niederlande)

Re: Externe Linux-Kommandos starten

Beitrag von marcov »

WIe gesagt, runcommand nutzen (dieser Variante kann aber 3.0+, also Lazarus 1.6+ sein),

Code: Alles auswählen

{$mode delphi}
uses process;
var outp  : string;
begin
 if RunCommand('/bin/ls',['-al'],outp,[poStderrToOutput]) then
   begin
    writeln(length(outp)); // immer auch Länge checken. Output kann falsch sein wenn es #0's enthaelt.                                   
    writeln(outp);             // wenn Länge >> sichtbare output dann weiß man das da etwas nicht stimmt.
  end;
end.
 
... geht hier tadellos. Debian stable.

AndreasMR
Beiträge: 98
Registriert: Di 4. Aug 2015, 15:29
OS, Lazarus, FPC: Linux, Raspbian, Windows
CPU-Target: 64/32 Bit

Re: Externe Linux-Kommandos starten

Beitrag von AndreasMR »

Hallo Marcov,

gerade habe ich den Code aus Beitrag #1 auf meinem Ubuntu-PC (Lazarus 1.4.2, FPC 2.6.4) eingegeben.

Dort funktioniert er tadellos! Ich habe etliche Linux-Kommandos getestet.

Das heißt, es liegt irgendwie an der Lazarus-Version, die auf dem Raspberry Pi installiert ist. Dann werde ich da ansetzen müssen.


Groetjes naar Eindhoven

Andreas
Ubuntu 14.04 LTS / Raspbian / Windows: Lazarus ab 0.9 bis 3.0

AndreasMR
Beiträge: 98
Registriert: Di 4. Aug 2015, 15:29
OS, Lazarus, FPC: Linux, Raspbian, Windows
CPU-Target: 64/32 Bit

Re: [gelöst] Externe Linux-Kommandos starten

Beitrag von AndreasMR »

Hallo zusammen,

es wurde nochmals komplizierter.

Lazarus habe ich zum einen auf einem Ubuntu-PC installiert, zum anderen aber auch auf einem Raspberry Pi (u.a. Modell B+ in der Version 0.9.30.4). Beim Versuch, ein Update zu erzeugen hatte ich massive Probleme wegen eines Versions-Mischmaschs. Nachdem ich Lazarus und FPC restlos vom System entfernt hatte, gelang mir dann eine Installtion von Lazarus 0.9.30.4 und FPC 2.6.0. Dieses kann ich allerdings nur noch als User root nutzen. Aber egal.

Als ich dann die Programmierung fortsetzen konnte, ist mir aufgefallen, dass das Verhalten auf PC und Raspberry Pi recht unterschiedlich ist.

Mir ist dann aufgefallen, dass die eigentlichen Probleme, die zu diesem Thread geführt haben, daran liegen, dass in der Ausgabe der Linux-Kommandos Umlaute enthalten sind. Auf dem PC habe ich das dann recht einfach zum Laufen bekommen - das gleiche Programm auf dem Raspberry Pi zeigte Zeilen, die Umlauten enthalten, einfach gar nicht an.

Da ich die externen Kommandos auch in Verbindung mit Pipes starten wollte, habe ich mir dann etwas ganz anderes überlegt. Wenn jemand Ideen hat, wie man dies (beliebig komplizierte Pipes in Lazarus) einfacher oder sinnvoller realisieren kann, dann bitte her mit Euren Ideen. :)

Als Erstes habe ich ein kleines Programm in der Programmiersprache Icon geschrieben (in der komme ich immer noch am besten zurecht):

Code: Alles auswählen

 
$define PFAD "/mnt/RAMDisk/icon-executor.out"    # definiert einen Pfad, in dem die Pipe-Ausgabe gespeichert wird
 
procedure main(arg)                        # Übergabe eines Argumentes oder einer Argumentenliste
    command := ""                           # Kommando auf leere Zeichenkette setzen
    every command ||:= !arg || " "          # Jedes Argument an die Zeichenkette command anhängen
    if pipe := open(command, "p") then      # Eine Pipe öffnen
    {   if fh := open(PFAD, "w") then       # Die Ausgabedatei für dieses Programm zum Schreiben öffnen
        {   write(fh, command)              # Ausgabe des Linux-Kommandos / Pipe-Kommandos
            write(fh, repl("-", *command))  # Minus-Zeichen unter das Kommando
            while write(fh, read(pipe))     # Ausgabe der Pipe solange auslesen, wie es geht und jede Zeile in die geöffnete Datei schreiben 
            close(fh)                       # Ausgabe-Datei schließen
        }
        close(pipe)                         # Pipe schließen
    }
end
 
Dann habe ich mich wieder Lazarus zugewandt.

Erfolg in dem Sinne, dass JEDES Linux-Kommando und beliebig komplizierte Pipes vollständig in einem Memo-Feld angezeigt werden, hatte ich mit folgendem Code:

Code: Alles auswählen

 
procedure TForm1.Edit2EditingDone(Sender: TObject);
var     AProcess    : TProcess;
        AStringList : TStringList;
        Datei       : TextFile;
        s           : String;
        i           : integer;
 
begin
    AProcess := TProcess.Create(nil);
    AProcess.CommandLine := '/home/pi/icon9_51/bin/icon-executor ' + Edit2.Text;
    AProcess.Options := AProcess.Options + [poWaitOnExit, poStderrToOutPut];
    AProcess.Execute;
    AProcess.Free;
 
    AStringList := TStringList.Create;
    AStringList.LoadFromFile('/mnt/RAMDisk/icon-executor.out');
    for i := 0 to AStringList.count - 1 do
    begin
        AStringList[i] := UTF8Encode(AStringList[i]);
        Memo1.Lines.Add(AStringList[i]);
    end;
    AStringList.Free;
 
Der Ausschnitt

Code: Alles auswählen

 
    AStringList.LoadFromFile('/mnt/RAMDisk/icon-executor.out');
    for i := 0 to AStringList.count - 1 do
    begin
        AStringList[i] := UTF8Encode(AStringList[i]);
        Memo1.Lines.Add(AStringList[i]);
    end;
gefällt mir persönlich nicht so - aber verschiedene andere Ansätze haben nicht funktioniert.


Beste Grüße

Andreas
Ubuntu 14.04 LTS / Raspbian / Windows: Lazarus ab 0.9 bis 3.0

Antworten