Executing External Programs/de: what changed
Revision 1 to 2. Removed lines have a minus sign; added lines have a plus sign.
| Old | New | Line | |
|---|---|---|---|
| 1 | 1 | Zurück zu den [[Additional information/de|Zusätzlichen Informationen]]. | |
| 2 | 2 | ||
| 3 | 3 | ## Einleitung | |
| 4 | 4 | ||
| 5 | 5 | Es gibt verschiedene Wege, ein externes Programm auszuführen, aber ich will mich hier auf einen konzentrieren: [[doc:fcl/process/tprocess.html|TProcess]]. | |
| 6 | 6 | ||
| 7 | 7 | Wenn Sie bereits **ShellExecute** und/oder **WinExec** in Delphi verwenden, dann können Sie damit beginnen, TProcess als Alternative in FPC/Lazarus zu verwenden (dies ist auch gültig, wenn Sie Lazarus unter Linux starten, weil TProcess plattformübergreifend ist). | |
| 8 | 8 | ||
| 9 | 9 | > **Note:** Die englische Variante dieser Seite ist teilweise ausführlicher(+: MSWindows: CreateProcess, ShellExecute and WinExec + Unix fpsystem, fpexecve and shell + macOS + Run detached program und auch danach noch einiges mehr) | |
| 10 | 10 | ||
| 11 | 11 | > **Note:** FPC/Lazarus bietet Unterstützung für **ShellExecute** und/oder **WinExec**, aber diese Unterstützung gibt es nur unter Win32. Wenn Ihr Programm plattformunabhängig sein soll, dann ist die Verwendung von TProcess der beste Weg! | |
| 12 | 12 | ||
| 13 | 13 | ## Überblick : Vergleich | |
| 14 | 14 | ||
| 15 | 15 | | Unit | Methode | Plattformen | Befehlszeile | Features und Anmerkungen | | |
| 16 | 16 | |----|----|----|----|----| | |
| 17 | 17 | | SysUtils | [ExecuteProcess](https://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html) | Cross-Platform | Ja | Sehr limitiert, synchron | | |
| 18 | 18 | | ShellApi | [ShellExecute](https://msdn.microsoft.com/en-us/library/windows/desktop/bb762153%28v=vs.85%29.aspx) | Nur MS Windows | Ja | Viele. Kann Programme mit Admin Rechten starten. | | |
| 19 | 19 | | | [WinExec](https://msdn.microsoft.com/en-us/library/windows/desktop/ms687393%28v=vs.85%29.aspx) | Nur MS Windows | Ja | Kompatibilität mit 16-bit-Anwendungen | | |
| 20 | 20 | | | [CreateProcess](https://msdn.microsoft.com/en-us/library/windows/desktop/ms682425%28v=vs.85%29.aspx) | Nur MS Windows | Ja | Voll | | |
| 21 | 21 | | Unix | [fpsystem](https://www.freepascal.org/docs-html/rtl/unix/fpsystem.html) | Nur Unix | Ja | Ruft die Systemshell `/bin/sh` auf. | | |
| 22 | 22 | | BaseUnix | [fpexecve](https://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html) | Nur Unix | Nein | Vollständige Kontrolle über Parameter und Umgebungsvariablen. Aktuelles Programm wird nicht weiter ausgeführt. | | |
| 23 | 23 | | Process | [TProcess](https://www.freepascal.org/docs-html/fcl/process/tprocess.html) | Cross-Platform | Nein | Voll | | |
| 24 | 24 | | Process | [RunCommand](https://www.freepascal.org/docs-html/fcl/process/runcommand.html), [RunCommandInDir](https://www.freepascal.org/docs-html/fcl/process/runcommandindir.html) | Cross-Platform **Benötigt FPC 2.6.2+** | Ja | Wie TProcess | | |
| 25 | 25 | | LCLIntf | [OpenDocument](https://lazarus-ccr.sourceforge.io/docs/lcl/lclintf/opendocument.html) | Cross-Platform | Ja | Nur Dokument öffnen | | |
| 26 | 26 | | LCLIntf | [OpenURL](https://lazarus-ccr.sourceforge.io/docs/lcl/lclintf/openurl.html) | Cross-Platform | Ja | URL öffnen | | |
| 27 | 27 | ||
| 28 | 28 | ## (Process.)RunCommand | |
| 29 | 29 | ||
| 30 | 30 | - [RunCommand](http://www.freepascal.org/docs-html/fcl/process/runcommand.html) siehe dort | |
| 31 | 31 | - [RunCommandInDir](http://www.freepascal.org/docs-html/fcl/process/runcommandindir.html) siehe dort | |
| 32 | 32 | ||
| 33 | 33 | Mit FPC 2.6.2 wurden für TProcess einige Hilfsfunktionen zur Unit Process hinzugefügt basierend auf Wrapper im [[Projects using Lazarus|fpcup]]-Projekt. | |
| 34 | 34 | ||
| 35 | 35 | Diese Funktionen sind grundlegend und sofort benutzbar, sowohl wenn als Ergebnis des Kommandos ein einfacher String als auch wenn ein längeres Ergebnis zu erwarten ist. | |
| 36 | 36 | ||
| 37 | 37 | Ein einfaches Beispiel: | |
| 38 | 38 | ||
| 39 | 39 | ``` pascal | |
| 40 | 40 | program project1; | |
| 41 | 41 | ||
| 42 | 42 | {$mode objfpc}{$H+} | |
| 43 | 43 | ||
| 44 | 44 | uses | |
| 45 | 45 | Process; | |
| 46 | 46 | ||
| 47 | 47 | var | |
| 48 | 48 | s : ansistring; | |
| 49 | 49 | ||
| 50 | 50 | begin | |
| 51 | 51 | ||
| 52 | 52 | if RunCommand('/bin/bash',['-c','echo $PATH'],s) then | |
| 53 | 53 | writeln(s); | |
| 54 | 54 | ||
| 55 | 55 | end. | |
| 56 | 56 | ``` | |
| 57 | 57 | ||
| 58 | 58 | Aber bitte beachten Sie, dass nicht alle 'built-in' Aufrufe (z. B. in Linux Alias) funktionieren,\ | |
| 59 | 59 | 1.) weil Alias-Werte nur in interaktiven Shells standardmäßig expandiert werden und\ | |
| 60 | 60 | 2.) weil auch .bashrc nur in interaktiven Shells gelesen wird, es sei denn die BASH_ENV Environment Variable ist gesetzt.\ | |
| 61 | 61 | So ergibt z. B. folgendes Programm keinen Output. | |
| 62 | 62 | ||
| 63 | 63 | ``` pascal | |
| 64 | 64 | program project2; | |
| 65 | 65 | ||
| 66 | 66 | {$mode objfpc}{$H+} | |
| 67 | 67 | ||
| 68 | 68 | uses | |
| 69 | 69 | Process; | |
| 70 | 70 | ||
| 71 | 71 | var | |
| 72 | 72 | s : ansistring; | |
| 73 | 73 | ||
| 74 | 74 | begin | |
| 75 | 75 | ||
| 76 | 76 | if RunCommand('/bin/bash',['-c','alias'],s) then | |
| 77 | 77 | writeln(s); | |
| 78 | 78 | ||
| 79 | 79 | end. | |
| 80 | 80 | ``` | |
| 81 | 81 | ||
| 82 | 82 | Eine überladene Variante von RunCommand liefert zusätzlich den Exitcode des Programs. RunCommandInDir setzt noch zusätzlich das Standardverzeichnis (setzt p.CurrentDirectory): | |
| 83 | 83 | ||
| 84 | 84 | ``` pascal | |
| 85 | 85 | function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer; | |
| 86 | 86 | function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean; | |
| 87 | 87 | function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean; | |
| 88 | 88 | ``` | |
| 89 | 89 | ||
| 90 | 90 | ## SysUtils.ExecuteProcess | |
| 91 | 91 | ||
| 92 | 92 | Wenn Sie keine Pipes oder sonstige Kontrollmechanismen benötigen, kann das Ausführen externer Programme über folgenden Befehl erreicht werden: | |
| 93 | 93 | ||
| 94 | 94 | ``` pascal | |
| 95 | 95 | SysUtils.ExecuteProcess( UTF8ToSys( '/full/path/to/binary'), '''' '', []); | |
| 96 | 96 | ``` | |
| 97 | 97 | ||
| 98 | 98 | Leider wird der aufrufende Prozess synchron ausgeführt: er "hängt", wartet also, bis das aufgerufene Programm beendet wurde. Es ist deshalb besser, den Befehl CreateProcess() zu verwenden, wie es hier beschrieben ist: | |
| 99 | 99 | ||
| 100 | - | ||
| 100 | + | ```text | |
| 101 | + | http://msdn.microsoft.com/en-us/library/ms682512%28v=vs.85%29.aspx | |
| 102 | + | ``` | |
| 101 | 103 | ||
| 102 | 104 | ## TProcess | |
| 103 | 105 | ||
| 104 | 106 | Man kann TProcess benutzen, um externe Programme zu starten. Vorteile von TProcess sind | |
| 105 | 107 | ||
| 106 | 108 | - Plattform-Unabhängigkeit | |
| 107 | 109 | - die Fähigkeit, von *stdout* zu lesen und auf *stdin* zu schreiben. | |
| 108 | 110 | ||
| 109 | 111 | Anmerkung: TProcess ist kein Terminal bzw. keine Shell! Weder können Skripte direkt ausgeführt werden, noch kann die Ausgabe mit Operatoren wie "\|", "\>", "\<", "&" etc. umgeleitet werden. Es ist möglich, die gleichen Ergebnisse mit TProcess und FreePascal zu erzielen. Ein paar Beispiele finden Sie weiter unten. | |
| 110 | 112 | ||
| 111 | 113 | Wichtig: Sie müssen den vollständigen Pfad zum ausführbaren Programm angeben. Zum Beispiel '/bin/cp' anstelle von 'cp'. Wenn das Programm im Vorgabepfad steht, dann können Sie die Funktion [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] aus der Unit [[doc:lcl/fileutil/index.html|FileUtil]] der LCL dazu benutzen. | |
| 112 | 114 | ||
| 113 | 115 | ### Ein einfaches Beispiel | |
| 114 | 116 | ||
| 115 | 117 | ``` pascal | |
| 116 | 118 | // Dies ist ein Demoprogramm, das zeigt, wie man ein | |
| 117 | 119 | // externes Programm startet. | |
| 118 | 120 | program launchprogram; | |
| 119 | 121 | ||
| 120 | 122 | // Hier binden wir Dateien ein, die hilfreiche Funktionen | |
| 121 | 123 | // und Prozeduren haben, die wir benötigen. | |
| 122 | 124 | uses | |
| 123 | 125 | Classes, SysUtils, Process; | |
| 124 | 126 | ||
| 125 | 127 | // Dies definiert die Variable "AProcess" als eine Variable | |
| 126 | 128 | // vom Typ "TProcess" | |
| 127 | 129 | var | |
| 128 | 130 | AProcess: TProcess; | |
| 129 | 131 | ||
| 130 | 132 | // Hier beginnt der Befehlstext: | |
| 131 | 133 | begin | |
| 132 | 134 | // Nun erstellen wir das Objekt TProcess und | |
| 133 | 135 | // weisen es der Variable AProcess zu. | |
| 134 | 136 | AProcess := TProcess.Create(nil); | |
| 135 | 137 | ||
| 136 | 138 | // Lassen sie uns den FreePascal Compiler verwenden. | |
| 137 | 139 | // Dazu müssen wir den Programmnamen an AProcess | |
| 138 | 140 | // übergeben: | |
| 139 | 141 | AProcess.Executable:= 'ppc386'; | |
| 140 | 142 | ||
| 141 | 143 | // An das Programm soll der Parameter -h übergeben werden: | |
| 142 | 144 | AProcess.Parameters.Add('-h'); | |
| 143 | 145 | ||
| 144 | 146 | // Während das externe Programm läuft, soll unser | |
| 145 | 147 | // Programm natürlich nicht weiterlaufen. | |
| 146 | 148 | // Dies regeln wir mit folgender Bedingung: | |
| 147 | 149 | AProcess.Options := AProcess.Options + [poWaitOnExit]; | |
| 148 | 150 | ||
| 149 | 151 | // Nun muss AProcess noch ausgeführt werden... | |
| 150 | 152 | AProcess.Execute; | |
| 151 | 153 | ||
| 152 | 154 | // Wegen der oben gesetzter Bedingung wird dieser | |
| 153 | 155 | // Codeabschnitt erst erreicht, wenn ppc386 beendet | |
| 154 | 156 | // ist: | |
| 155 | 157 | AProcess.Free; | |
| 156 | 158 | end. | |
| 157 | 159 | ``` | |
| 158 | 160 | ||
| 159 | 161 | Das war's. Sie haben gerade gelernt, wie Sie in Ihrem Programm ein externes Programm aufrufen. | |
| 160 | 162 | ||
| 161 | 163 | ### Ein verbessertes Beispiel | |
| 162 | 164 | ||
| 163 | 165 | Das ist nett, aber wie lese ich den Output von einem Programm, das ich laufen habe? | |
| 164 | 166 | ||
| 165 | 167 | Lassen Sie uns unser Beispiel ein wenig erweitern und einfach das tun: | |
| 166 | 168 | ||
| 167 | 169 | ``` pascal | |
| 168 | 170 | // Dies ist ein Demoprogramm, das zeigt, wie man ein externes | |
| 169 | 171 | // Programm startet und dessen Output einliest. | |
| 170 | 172 | program launchprogram; | |
| 171 | 173 | ||
| 172 | 174 | // Hier binden wir Dateien ein, die hilfreiche Funktionen | |
| 173 | 175 | // und Prozeduren haben, die wir benötigen. | |
| 174 | 176 | uses | |
| 175 | 177 | Classes, SysUtils, Process; | |
| 176 | 178 | ||
| 177 | 179 | // Dies definiert die Variable "AProcess" als eine | |
| 178 | 180 | // Variable vom Typ "TProcess" | |
| 179 | 181 | // Jetzt fügen wir auch eine TStringList hinzu, um die vom | |
| 180 | 182 | // Programm ausgegebenen Daten zu speichern | |
| 181 | 183 | var | |
| 182 | 184 | AProcess: TProcess; | |
| 183 | 185 | AStringList: TStringList; | |
| 184 | 186 | ||
| 185 | 187 | // Ab hier startet Ihr Programm | |
| 186 | 188 | begin | |
| 187 | 189 | // Jetzt erzeugen wir das TProcess Objekt und | |
| 188 | 190 | // ordnen es der Variablen AProcess zu. | |
| 189 | 191 | AProcess := TProcess.Create(nil); | |
| 190 | 192 | ||
| 191 | 193 | // Erzeugen des TStringList Objekts. | |
| 192 | 194 | AStringList := TStringList.Create; | |
| 193 | 195 | ||
| 194 | 196 | // Gibt an, welcher Befehl vom Prozess ausgeführt werden soll | |
| 195 | 197 | // Lassen sie uns den FreePascal Compiler verwenden | |
| 196 | 198 | AProcess.CommandLine := 'ppc386 -h'; | |
| 197 | 199 | ||
| 198 | 200 | // Wir definieren eine Option, wie das Programm | |
| 199 | 201 | // ausgeführt werden soll. Dies stellt sicher, dass | |
| 200 | 202 | // unser Programm nicht vor Beendigung des aufgerufenen | |
| 201 | 203 | // Programmes fortgesetzt wird. Außerdem geben wir an, | |
| 202 | 204 | // dass wir die Ausgabe lesen wollen | |
| 203 | 205 | AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes]; | |
| 204 | 206 | ||
| 205 | 207 | // Startet den Prozess, nachdem die Parameter entsprechend | |
| 206 | 208 | // gesetzt sind | |
| 207 | 209 | AProcess.Execute; | |
| 208 | 210 | ||
| 209 | 211 | // Folgendes wird erst nach Beendigung von ppc386 ausgeführt | |
| 210 | 212 | ||
| 211 | 213 | // Die Ausgabe wird nun in die Stringliste gelesen | |
| 212 | 214 | AStringList.LoadFromStream(AProcess.Output); | |
| 213 | 215 | ||
| 214 | 216 | // Speichert den output in eine Datei. | |
| 215 | 217 | AStringList.SaveToFile('output.txt'); | |
| 216 | 218 | ||
| 217 | 219 | // Nun da die Datei gespeichert ist können wir | |
| 218 | 220 | // TStringList und TProcess freigeben. | |
| 219 | 221 | AStringList.Free; | |
| 220 | 222 | AProcess.Free; | |
| 221 | 223 | end. | |
| 222 | 224 | ``` | |
| 223 | 225 | ||
| 224 | 226 | ### Einlesen eines großen Outputs | |
| 225 | 227 | ||
| 226 | 228 | Im vorherigen Beispiel haben wir gewartet, bis das Programm beendet wurde. Dann haben wir gelesen, was das Programm in seinen output geschrieben hatte. Aber angenommen, das Programm schreibt eine Menge Daten in den Output, wird die Pipe voll und das Programm angehalten, weil es wartet, bis die Pipe geleert wird. Aber das aufrufende Programm liest nicht aus der Pipe, bis das aufgerufene Programm beendet wurde. Ein *dead lock* tritt auf. | |
| 227 | 229 | ||
| 228 | 230 | Das folgende Beispiel benutzt daher nicht poWaitOnExit, sondern liest den Output, während das Programm immer noch läuft. Der Output wird in einem TMemoryStream gespeichert, der später verwendet werden kann, um den Output in eine TStringList einzulesen. | |
| 229 | 231 | ||
| 230 | 232 | ``` pascal | |
| 231 | 233 | program procoutlarge; | |
| 232 | 234 | { | |
| 233 | 235 | Copyright (c) 2004 by Marc Weustink | |
| 234 | 236 | ||
| 235 | 237 | This example is creeated in the hope that it will be useful, | |
| 236 | 238 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 237 | 239 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | |
| 238 | 240 | } | |
| 239 | 241 | ||
| 240 | 242 | uses | |
| 241 | 243 | Classes, Process, SysUtils; | |
| 242 | 244 | ||
| 243 | 245 | const | |
| 244 | 246 | READ_BYTES = 2048; | |
| 245 | 247 | ||
| 246 | 248 | var | |
| 247 | 249 | S: TStringList; | |
| 248 | 250 | M: TMemoryStream; | |
| 249 | 251 | P: TProcess; | |
| 250 | 252 | n: LongInt; | |
| 251 | 253 | BytesRead: LongInt; | |
| 252 | 254 | ||
| 253 | 255 | begin | |
| 254 | 256 | // Wir können poWaitOnExit hier nicht nutzen, weil wir die | |
| 255 | 257 | // Größe des Outputs nicht kennen. In Linux ist die Größe der | |
| 256 | 258 | // Pipe 2kB. Wenn die Ausgabe größer ist, müssen die Daten | |
| 257 | 259 | // zwischenzeitlich ausgelesen werden. Dies ist nicht möglich, | |
| 258 | 260 | // wenn auf das Ende gewartet wird - ein Deadlock tritt auf. | |
| 259 | 261 | // | |
| 260 | 262 | // Ein temporärer Memorystream wird verwendet, um den output zu puffern. | |
| 261 | 263 | ||
| 262 | 264 | M := TMemoryStream.Create; | |
| 263 | 265 | BytesRead := 0; | |
| 264 | 266 | ||
| 265 | 267 | P := TProcess.Create(nil); | |
| 266 | 268 | P.CommandLine := 'ppc386 -va bogus.pp'; | |
| 267 | 269 | P.Options := [poUsePipes]; | |
| 268 | 270 | WriteLn('-- executing --'); | |
| 269 | 271 | P.Execute; | |
| 270 | 272 | while P.Running do | |
| 271 | 273 | begin | |
| 272 | 274 | // stellt sicher, dass wir Platz haben | |
| 273 | 275 | M.SetSize(BytesRead + READ_BYTES); | |
| 274 | 276 | ||
| 275 | 277 | // versuche, es zu lesen | |
| 276 | 278 | n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES); | |
| 277 | 279 | if n > 0 | |
| 278 | 280 | then begin | |
| 279 | 281 | Inc(BytesRead, n); | |
| 280 | 282 | Write('.') | |
| 281 | 283 | end | |
| 282 | 284 | else begin | |
| 283 | 285 | // keine Daten, warte 100 ms | |
| 284 | 286 | Sleep(100); | |
| 285 | 287 | end; | |
| 286 | 288 | end; | |
| 287 | 289 | // lese den letzten Teil | |
| 288 | 290 | repeat | |
| 289 | 291 | // stellt sicher, dass wir Platz haben | |
| 290 | 292 | M.SetSize(BytesRead + READ_BYTES); | |
| 291 | 293 | // versuche es zu lesen | |
| 292 | 294 | n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES); | |
| 293 | 295 | if n > 0 | |
| 294 | 296 | then begin | |
| 295 | 297 | Inc(BytesRead, n); | |
| 296 | 298 | Write('.'); | |
| 297 | 299 | end; | |
| 298 | 300 | until n <= 0; | |
| 299 | 301 | if BytesRead > 0 then WriteLn; | |
| 300 | 302 | M.SetSize(BytesRead); | |
| 301 | 303 | WriteLn('-- executed --'); | |
| 302 | 304 | ||
| 303 | 305 | S := TStringList.Create; | |
| 304 | 306 | S.LoadFromStream(M); | |
| 305 | 307 | WriteLn('-- linecount = ', S.Count, ' --'); | |
| 306 | 308 | for n := 0 to S.Count - 1 do | |
| 307 | 309 | begin | |
| 308 | 310 | WriteLn('| ', S[n]); | |
| 309 | 311 | end; | |
| 310 | 312 | WriteLn('-- end --'); | |
| 311 | 313 | S.Free; | |
| 312 | 314 | P.Free; | |
| 313 | 315 | M.Free; | |
| 314 | 316 | end. | |
| 315 | 317 | ``` | |
| 316 | 318 | ||
| 317 | 319 | ### Verwendung von input und output eines TProcess | |
| 318 | 320 | ||
| 319 | 321 | Siehe das Beispiel 'processdemo' im [Lazarus-CCR SVN](https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process). | |
| 320 | 322 | ||
| 321 | 323 | ### Hinweise für die Verwendung von TProcess | |
| 322 | 324 | ||
| 323 | 325 | Mit den Direktiven `{$IFDEF}` und `{$ENDIF}` können Sie das Programm je nach Betriebssystem auswählen. | |
| 324 | 326 | ||
| 325 | 327 | ``` pascal | |
| 326 | 328 | AProcess:TProcess.Create(nil) | |
| 327 | 329 | {$IFDEF WIN32} | |
| 328 | 330 | AProcess.Executable := 'calc.exe'; // Windows Rechner | |
| 329 | 331 | {$ENDIF} | |
| 330 | 332 | {$IFDEF LINUX} | |
| 331 | 333 | AProcess.Executable := 'kcalc'; // KDE Rechner | |
| 332 | 334 | {$ENDIF} | |
| 333 | 335 | AProcess.Execute; // alternativ können sie AProcess.Active := True verwenden | |
| 334 | 336 | ``` | |
| 335 | 337 | ||
| 336 | 338 | ### Beispiel von "talking" mit dem aspell Prozess | |
| 337 | 339 | ||
| 338 | 340 | Innerhalb des [pasdoc](http://pasdoc.sourceforge.net/) Quellcodes können Sie zwei Units finden, die die Rechtschreibung prüfen, in dem sie mit dem laufenden aspell-Prozess durch Pipes kommunizieren: | |
| 339 | 341 | ||
| 340 | 342 | - [PasDoc_ProcessLineTalk.pas unit](http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas) implementiert die Klasse *TProcessLineTalk*, einen Nachfahren von TProcess, der einfach benutzt werden kann, um eine "zeilenweise" Kommunikation mit einem laufenden Prozess zu ermöglichen. | |
| 341 | 343 | ||
| 342 | 344 | - [PasDoc_Aspell.pas units](http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas) implementiert die Klasse *TAspellProcess*, welche die Rechtschreibprüfung ausführt, indem sie den darunter liegenden Prozess von TProcessLineTalk nutzt, um aspell auszuführen und mit dem laufenden Prozess zu kommunizieren. | |
| 343 | 345 | ||
| 344 | 346 | Beide Units sind ziemlich unabhängig vom Rest der pasdoc Quellen, so dass sie als praktische Demo für die Nutzung von TProcess dienen können. | |
| 345 | 347 | ||
| 346 | 348 | ### Ersetzen von Shell-Operatoren wie "\| \< \>" | |
| 347 | 349 | ||
| 348 | 350 | Gelegentlich möchten Sie einen komplizierteren Befehl starten, der seine Daten an einen anderen Befehl oder eine Datei weiterleitet. Also etwa folgendes | |
| 349 | 351 | ||
| 350 | 352 | ``` pascal | |
| 351 | 353 | ShellExecute('ersterBefehl.exe | zweiterBefehl.exe'); | |
| 352 | 354 | ``` | |
| 353 | 355 | ||
| 354 | 356 | oder | |
| 355 | 357 | ||
| 356 | 358 | ``` pascal | |
| 357 | 359 | ShellExecute('dir > output.txt'); | |
| 358 | 360 | ``` | |
| 359 | 361 | ||
| 360 | 362 | Das mit TProcess auszuführen klappt nicht, also: | |
| 361 | 363 | ||
| 362 | 364 | ``` pascal | |
| 363 | 365 | // das klappt nicht | |
| 364 | 366 | Process.CommandLine := 'ersterBefehl.exe | zweiterBefehl.exe'; | |
| 365 | 367 | Process.Execute; | |
| 366 | 368 | ``` | |
| 367 | 369 | ||
| 368 | 370 | #### Warum die Verwendung von Spezialoperatoren zum Umleiten der Ausgabe nicht funktioniert | |
| 369 | 371 | ||
| 370 | 372 | TProcess ist keine Shell-Umgebung, sondern nur ein Prozess. Also nicht zwei Prozesse, nur einer. Dennoch ist die Umleitung der Ausgabe in der gewünschten Weise möglich. Siehe dazu den nächsten Abschnitt [[Executing External Programs/de|Wie leitet man die Ausgabe mit TProcess um]]. | |
| 371 | 373 | ||
| 372 | 374 | ### Wie leitet man die Ausgabe mit TProcess um | |
| 373 | 375 | ||
| 374 | 376 | Sie können die Ausgaben eines Befehls an einen anderen Befehl umleiten, indem Sie für **jeden** Befehl eine Instanz des TProcess verwenden. | |
| 375 | 377 | ||
| 376 | 378 | Hier ist ein Beispiel das die Umleitung von einem Prozess an einen anderen erläutert. Für die Umleitung der Ausgabe von einem Prozess zu einer Datei/einem Stream siehe das Beispiel [[Executing External Programs/de|Einlesen eines großen Outputs]] | |
| 377 | 379 | ||
| 378 | 380 | Sie können nicht nur die "normale" Ausgabe (auch als "stdout" bekannt) umleiten, sondern auch die Fehlerausgabe (stderr), wenn Sie die Option "poStderrToOutPut" angeben, wie in den Optionen des zweiten Prozesses ersichtlich. | |
| 379 | 381 | ||
| 380 | 382 | ``` pascal | |
| 381 | 383 | program Project1; | |
| 382 | 384 | ||
| 383 | 385 | uses | |
| 384 | 386 | Classes, sysutils, process; | |
| 385 | 387 | ||
| 386 | 388 | var | |
| 387 | 389 | FirstProcess, | |
| 388 | 390 | SecondProcess: TProcess; | |
| 389 | 391 | Buffer: array[0..127] of char; | |
| 390 | 392 | ReadCount: Integer; | |
| 391 | 393 | ReadSize: Integer; | |
| 392 | 394 | begin | |
| 393 | 395 | FirstProcess := TProcess.Create(nil); | |
| 394 | 396 | SecondProcess := TProcess.Create(nil); | |
| 395 | 397 | ||
| 396 | 398 | FirstProcess.Options := [poUsePipes]; | |
| 397 | 399 | SecondProcess.Options := [poUsePipes,poStderrToOutPut]; | |
| 398 | 400 | ||
| 399 | 401 | FirstProcess.CommandLine := 'pwd'; | |
| 400 | 402 | SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -'; | |
| 401 | 403 | // dies liefert das gleiche wie "pwd | grep / -" | |
| 402 | 404 | ||
| 403 | 405 | FirstProcess.Execute; | |
| 404 | 406 | SecondProcess.Execute; | |
| 405 | 407 | ||
| 406 | 408 | while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do | |
| 407 | 409 | begin | |
| 408 | 410 | if FirstProcess.Output.NumBytesAvailable > 0 then | |
| 409 | 411 | begin | |
| 410 | 412 | // stellen Sie sicher, dass Sie nicht mehr Daten lesen als im Puffer | |
| 411 | 413 | // bereitstehen | |
| 412 | 414 | ReadSize := FirstProcess.Output.NumBytesAvailable; | |
| 413 | 415 | if ReadSize > SizeOf(Buffer) then | |
| 414 | 416 | ReadSize := SizeOf(Buffer); | |
| 415 | 417 | // jetzt lesen wir die Ausgabe in den Puffer | |
| 416 | 418 | ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize); | |
| 417 | 419 | // und schreiben den Puffer an den zweiten Prozess | |
| 418 | 420 | SecondProcess.Input.Write(Buffer[0], ReadCount); | |
| 419 | 421 | ||
| 420 | 422 | // falls der SecondProcess große Datenmengen an seine Ausgabe sendet, dann | |
| 421 | 423 | // sollten wir diese Daten auslesen, um einen Deadlock zu vermeiden. | |
| 422 | 424 | // siehe das vorhergehende Beispiel "Einlesen eines großen Outputs" | |
| 423 | 425 | end; | |
| 424 | 426 | end; | |
| 425 | 427 | // Schließt die Eingabe an den SecondProcess, | |
| 426 | 428 | // sodass der die Verarbeitung seiner Daten beendet | |
| 427 | 429 | SecondProcess.CloseInput; | |
| 428 | 430 | ||
| 429 | 431 | // und warte auf seine Beendigung. | |
| 430 | 432 | // Sei achtsam, welcher Befehl ausgeführt werden soll, weil er nicht beendet würde, | |
| 431 | 433 | // wenn seine Eingabe geschlossen ist. Die folgende Zeile führt dann zu einer unendlichen Schleife | |
| 432 | 434 | while SecondProcess.Running do | |
| 433 | 435 | Sleep(1); | |
| 434 | 436 | // Das wars! Der Rest des Programms ist nur dazu da, dass das Beispiel | |
| 435 | 437 | // irgendwas 'nützliches' macht. | |
| 436 | 438 | ||
| 437 | 439 | // Wir benutzen wieder den Puffer, diesmal für die Ausgabe vom SecondProcess | |
| 438 | 440 | // an die Standardausgabe stdout dieses Beispielprogrammes. | |
| 439 | 441 | WriteLn('Grep output Start:'); | |
| 440 | 442 | ReadSize := SecondProcess.Output.NumBytesAvailable; | |
| 441 | 443 | if ReadSize > SizeOf(Buffer) then | |
| 442 | 444 | ReadSize := SizeOf(Buffer); | |
| 443 | 445 | if ReadSize > 0 then | |
| 444 | 446 | begin | |
| 445 | 447 | ReadCount := SecondProcess.Output.Read(Buffer, ReadSize); | |
| 446 | 448 | WriteLn(Copy(Buffer,0, ReadCount)); | |
| 447 | 449 | end | |
| 448 | 450 | else | |
| 449 | 451 | WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus); | |
| 450 | 452 | WriteLn('Grep output Finish:'); | |
| 451 | 453 | ||
| 452 | 454 | // free our process objects | |
| 453 | 455 | FirstProcess.Free; | |
| 454 | 456 | SecondProcess.Free; | |
| 455 | 457 | end. | |
| 456 | 458 | ``` | |
| 457 | 459 | ||
| 458 | 460 | Das war es. Nun können Sie Ausgaben von einem Programm an das nächste umleiten. | |
| 459 | 461 | ||
| 460 | 462 | #### Anmerkungen | |
| 461 | 463 | ||
| 462 | 464 | Dieses Beipiel könnte übertrieben erscheinen, weil es ermöglicht, mit TProcess auch "komplizierte" Befehle unter Verwendung einer Shell zu starten, wie: | |
| 463 | 465 | ||
| 464 | 466 | ``` pascal | |
| 465 | 467 | Process.Commandline := 'sh -c "pwd | grep / -"'; | |
| 466 | 468 | ``` | |
| 467 | 469 | ||
| 468 | 470 | Aber unser Beispiel ist dadurch wirklich plattformübergreifend. Es braucht keinerlei Modifikation um unter Windows oder Linux etc. ausgeführt zu werden. Der Befehl "sh" ist unter Ihrem Betriebssystem möglicherweise nicht verfügbar, im Allgemeinen gibt es ihn nur auf *nix Systemen.\ | |
| 469 | 471 | Ebenso haben wir in unserem Beispiel mehr Flexibilität gewonnen, da wir zu und von der Eingabe, der Ausgabe und zu 'stderr' lesen und schreiben können, und das individuell für jeden Prozess. Das könnte in Ihrem Projekt sehr vorteilhaft sein. | |
| 470 | 472 | ||
| 471 | 473 | ### Parameter, die Leerzeichen enthalten (Replacing Shell Quotes) | |
| 472 | 474 | ||
| 473 | 475 | In der Linux-Shell ist es möglich, Argumente in Anführungszeichen zu schreiben, ähnlich dem Folgenden: | |
| 474 | 476 | ||
| 475 | - | ||
| 477 | + | ```text | |
| 478 | + | gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram | |
| 479 | + | ``` | |
| 476 | 480 | ||
| 477 | 481 | Und GDB erhält dadurch 3 Argumente (zusätzlich zum ersten Argument, das den vollständigen Pfad zum ausführbaren Programm enthält): | |
| 478 | 482 | ||
| 479 | 483 | 1. --batch | |
| 480 | 484 | 2. --eval-command=info symbol 0x0000DDDD | |
| 481 | 485 | 3. the full path to myprogram | |
| 482 | 486 | ||
| 483 | 487 | TProcess und auch (???) übergeben Parameter mit Leerzeichen, aber sie benutzen einen unterschiedlichen Stil dazu. Anstatt nur Teile des Parameters in Anführungszeichen zu setzen, schließen sie alles ein. Ähnlich wie: | |
| 484 | 488 | ||
| 485 | 489 | ``` pascal | |
| 486 | 490 | TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram'; | |
| 487 | 491 | ``` | |
| 488 | 492 | ||
| 489 | 493 | Und beachten Sie auch, dass Sie nur vollständige Pfade übergeben. | |
| 490 | 494 | ||
| 491 | 495 | Siehe dazu auch die Diskussion darüber unter: <http://bugs.freepascal.org/view.php?id=14446> | |
| 492 | 496 | ||
| 493 | 497 | ### RunCommand Erweiterungen | |
| 494 | 498 | ||
| 495 | 499 | Im FPC 3.2.0+ wurde die RunCommand implementation verbessert und in die TProcess Klasse eingefügt. Dies erlaubt eine schnellere Erstellung eigener Speziallisierungen der TProcess Klasse. Hier ein Beispiel mit Timeout: | |
| 496 | 500 | ||
| 497 | 501 | ``` pascal | |
| 498 | 502 | program TestTProcessTimeout; | |
| 499 | 503 | ||
| 500 | 504 | uses classes, sysutils, process, dateutils; | |
| 501 | 505 | ||
| 502 | 506 | type | |
| 503 | 507 | { TProcessTimeout } | |
| 504 | 508 | TProcessTimeout = class(TProcess) | |
| 505 | 509 | protected | |
| 506 | 510 | timeoutperiod: TTime; | |
| 507 | 511 | timedout : boolean; | |
| 508 | 512 | started : TDateTime; | |
| 509 | 513 | procedure LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string); | |
| 510 | 514 | public | |
| 511 | 515 | class function RunCommandwithTimeout(const exename:TProcessString;const commands:array of TProcessString;const dir:string;out outputstring:string;out errorstring:string;out exitstate:integer; ProcessOptions : TProcessOptions = [];SWOptions:TShowWindowOptions=swoNone;timeout:integer=15):boolean;static; | |
| 512 | 516 | end; | |
| 513 | 517 | ||
| 514 | 518 | procedure TProcessTimeout.LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string); | |
| 515 | 519 | begin | |
| 516 | 520 | if status=RunCommandIdle then | |
| 517 | 521 | begin | |
| 518 | 522 | writeln(Executable+'('+ Parameters.CommaText+')' +' time: '+TimeToStr(now-started)); | |
| 519 | 523 | if (now-started)>timeoutperiod then | |
| 520 | 524 | begin | |
| 521 | 525 | writeln('process timed out '); | |
| 522 | 526 | timedout:=true; | |
| 523 | 527 | Terminate(255); | |
| 524 | 528 | exit; | |
| 525 | 529 | end; | |
| 526 | 530 | sleep(RunCommandSleepTime); | |
| 527 | 531 | end; | |
| 528 | 532 | end; | |
| 529 | 533 | ||
| 530 | 534 | class function TProcessTimeout.RunCommandwithTimeout( | |
| 531 | 535 | const exename : TProcessString; | |
| 532 | 536 | const commands : array of TProcessString; | |
| 533 | 537 | const dir : string; | |
| 534 | 538 | out outputstring : string; | |
| 535 | 539 | out errorstring : string; | |
| 536 | 540 | out exitstate : integer; | |
| 537 | 541 | ProcessOptions : TProcessOptions = []; | |
| 538 | 542 | SWOptions : TShowWindowOptions=swoNone; | |
| 539 | 543 | timeout : integer=15 | |
| 540 | 544 | ):boolean; | |
| 541 | 545 | Var | |
| 542 | 546 | p : TProcessTimeout; | |
| 543 | 547 | i : integer; | |
| 544 | 548 | begin | |
| 545 | 549 | p:=TProcessTimeout.create(nil); | |
| 546 | 550 | p.OnRunCommandEvent:=@p.LocalnIdleSleep; | |
| 547 | 551 | p.CurrentDirectory:=dir; | |
| 548 | 552 | ||
| 549 | 553 | //timeout in Minuten | |
| 550 | 554 | p.timeoutperiod:=timeout/MinsPerDay; | |
| 551 | 555 | ||
| 552 | 556 | (* | |
| 553 | 557 | //Wenn der Timeout in sekunden angegeben werden soll dies hier ausklammern | |
| 554 | 558 | p.timeoutperiod:=timeout/SecsPerDay; | |
| 555 | 559 | *) | |
| 556 | 560 | ||
| 557 | 561 | //Jede sekunde überprüfen. | |
| 558 | 562 | p.runcommandsleeptime := 1000; | |
| 559 | 563 | ||
| 560 | 564 | if ProcessOptions<>[] then | |
| 561 | 565 | P.Options:=ProcessOptions - [poRunSuspended,poWaitOnExit]; | |
| 562 | 566 | p.options:=p.options+[poRunIdle]; // needed to run the RUNIDLE event. See User Changes 3.2.0 | |
| 563 | 567 | ||
| 564 | 568 | P.ShowWindow:=SwOptions; | |
| 565 | 569 | p.Executable:=exename; | |
| 566 | 570 | if high(commands)>=0 then | |
| 567 | 571 | for i:=low(commands) to high(commands) do | |
| 568 | 572 | p.Parameters.add(commands[i]); | |
| 569 | 573 | p.timedout:=false; | |
| 570 | 574 | p.started:=now; | |
| 571 | 575 | try | |
| 572 | 576 | // RunCommandLoop ruft alle runcommandsleeptime Millisekcunden LocalnIdleSleep auf. | |
| 573 | 577 | RunCommandwithTimeout:=p.RunCommandLoop(outputstring,errorstring,exitstate)=0; | |
| 574 | 578 | if p.timedout then | |
| 575 | 579 | RunCommandwithTimeout:=false; | |
| 576 | 580 | finally | |
| 577 | 581 | p.free; | |
| 578 | 582 | end; | |
| 579 | 583 | if exitstate<>0 then RunCommandwithTimeout:=false; | |
| 580 | 584 | end; | |
| 581 | 585 | ||
| 582 | 586 | // Beispiel: | |
| 583 | 587 | ||
| 584 | 588 | var | |
| 585 | 589 | output,errors : string; | |
| 586 | 590 | exitcode :integer; | |
| 587 | 591 | begin | |
| 588 | 592 | //Windows und Unix haben andere Programme | |
| 589 | 593 | {$if defined(WINDOWS)} | |
| 590 | 594 | if TProcessTimeout.RunCommandwithTimeout('cmd',['/c','dir','C:*.exe','/s'],'c:\',output,errors,exitcode,[],swoNone,1) then // dir c:*.exe /s | |
| 591 | 595 | {$else$} | |
| 592 | 596 | if TProcessTimeout.RunCommandwithTimeout('find',['/','-name','"*.sh"'],'/',output,errors,exitcode,[],swoNone,1) then // find / -name "*.sh" | |
| 593 | 597 | {$endif} | |
| 594 | 598 | begin | |
| 595 | 599 | writeln('Programm fertig'); | |
| 596 | 600 | writeln('--------------------Ausgabe-------------------'); | |
| 597 | 601 | writeln(output); | |
| 598 | 602 | writeln('--------------------Fehler--------------------'); | |
| 599 | 603 | writeln(errors); | |
| 600 | 604 | if not(exitcode=0) then | |
| 601 | 605 | begin | |
| 602 | 606 | writeln('-Programm mit Fehler beendet (exitcode: '+exitcode.tostring+')-'); | |
| 603 | 607 | end; | |
| 604 | 608 | readln(); | |
| 605 | 609 | end | |
| 606 | 610 | else | |
| 607 | 611 | begin | |
| 608 | 612 | writeln('Programm abgebrochen'); | |
| 609 | 613 | writeln('--------------------Ausgabe-------------------'); | |
| 610 | 614 | writeln(output); | |
| 611 | 615 | writeln('--------------------Fehler--------------------'); | |
| 612 | 616 | writeln(errors); | |
| 613 | 617 | writeln('-------------------EXITCODE------------------'); | |
| 614 | 618 | writeln(exitcode); | |
| 615 | 619 | readln(); | |
| 616 | 620 | end; | |
| 617 | 621 | end. | |
| 618 | 622 | ``` | |
| 619 | 623 | ||
| 620 | 624 | ## Siehe auch | |
| 621 | 625 | ||
| 622 | 626 | - [[TXMLPropStorage]] | |
| 623 | 627 | ||
| 624 | 628 | --- | |
| 625 | 629 | *From the Free Pascal wiki, page "Executing External Programs/de", as it stood on 2023-04-03 after a change by Yourlahr, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
