Sign in Join
Wiki copytemporary

Executing External Programs/ja: what changed

Revision 2 to 3. Removed lines have a minus sign; added lines have a plus sign.

OldNewLine
11## 概要
22
33外部のプログラムを動かすにはいくつかの方法がありますが、このチュートリアルでは [[doc:fcl/process/tprocess.html|TProcessを]]中心に紹介しています。
44
55Delphiで ShellExecute や WinExec を使っていたなら、FPC/Lazarusにおける代替としてTProcessを使用するようにしましょう。(TProcess は cross-platformであり、Linuxでも有効です。)
66
77Note: FPC/Lazarus は ShellExecute 及び WinExec をサポートしていますが、win32のみで有効です。 あなたのプログラムが cross-platformを目標にしているなら、TProcessの使用が最善です! 次に、さまざまな手法の比較を表にします
88
99方法
1010
1111プラットフォーム
1212
1313一行で書けるか
1414
1515機能の程度
1616
1717SysUtils.ExecuteProcess
1818
1919クロスプラットフォーム
2020
2121Yes
2222
2323極めて限定的、同期的
2424
2525(ShellApi) ShellExecute
2626
2727MS Windows のみ
2828
2929Yes
3030
3131多数。より高位の/管理者権限で実行可能
3232
3333Unix fpsystem, fpexecve
3434
3535Unix のみ
3636
3737TProcess
3838
3939クロスプラットフォーム
4040
4141No
4242
4343全機能が可能
4444
4545RunCommand(InDir)
4646
4747クロスプラットフォーム **FPC 2.6.2以降が必要**
4848
4949Yes
5050
5151よくあるTProcess利用法をカバー
5252
5353(LCLIntf) OpenDocument
5454
5555クロスプラットフォーム
5656
5757Yes
5858
5959文書を開くだけ
6060
6161## SysUtils.ExecuteProcess
6262
6363(クロスプラットフォーム)\
6464最も簡単な方法は、SysUtils.ExecuteProcessを使用することです。ただし、モーダル(訳注: 外部プログラムに制御が移ること。下記)ですし、パイプもいかなるコントロールも使えません:
6565
6666``` pascal
6767SysUtils.ExecuteProcess(UTF8ToSys('実行ファイルへのフルパス'), '', [])
6868```
6969
7070呼出しは同期的です: 呼び出した側は、外部プログラムの動作が終了するまで「ハング」します - しかしアプリケーションの実行を進める前に、ユーザになにかをさせるには便利かもしれません。次の節の **TProcess** はもっと広い用途に用いることができ、おすすめです。Windows だけでいいなら、**ShellExecute** も使えます。
7171
7272- [ExecuteProcess reference](http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html)
7373
7474## MS Windows : CreateProcess, ShellExecute と WinExec
7575
7676> **Note:** FPC/Lazarus は **CreateProcess**、**ShellExecute**、**WinExec** をサポートしてはいますが、プラットフォームは Win32/64 に限定されます。クロスプラットフォームなプログラムには **TProcess** をお使いください。
7777
7878> **Note:** WinExec は 16 bit 呼出しで、何年も前から Windows API の中で旧式化しています。最近の FPC は警告を発します。
7979
8080**ShellExecute** は MS Windows で標準的な関数です (ShellApi.h)。マイクロソフトによる解説は \[<http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85>).aspx documentation on MSDN\] をどうぞ。この関数が不安定だと思う方は、この解説の中の COM の初期化についての注釈をお読み下さい。
8181
8282``` pascal
8383uses ..., ShellApi;
8484
8585// 簡単な一行野郎(エラーを無視) :
8686if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;
8787
8888// バッチファイルを実行 :
8989if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;
9090
9191// 任意のフォルダでコマンドプロンプトを開く :
9292if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;
9393
9494// (隠れたコマンドウィンドウを通して)start コマンドを使ってデフォルトブラウザでウェブページの URL を開く:
9595if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;
9696
9797// あるいは便利な手続:
9898procedure RunShellExecute(const prog,params:string);
9999begin
100100 // ( ハンドル, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' は必ずしも必要ではない
101101 // パス付きプログラム名, 引数, 作業用フォルダ,
102102 // 0=隠し / 1=SW_SHOWNORMAL / 3=最大化 / 7=最小化) //定数 SW_ は Windows ユニットにある
103103 if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //成功
104104 // 0..32 はエラー
105105end;
106106```
107107
108108WideChar 版の ShellExecuteExW や AnsiChar 版の ShellExecuteExA もあります。
109109
110110The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.
111111
112112If in Delphi you used ShellExecute for **documents** like Word documents or URLs, have a look at the open* (OpenURL etc) functions in lclintf (see the Alternatives section lower down this page).
113113
114114### Using ShellExecuteEx for elevation/administrator permissions
115115
116116If you need to execute external program with administrator/elevated privileges, you can use the **runas** method with the alternative ShellExecuteEx function:
117117
118118``` pascal
119119uses ShellApi, ...;
120120
121121function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;
122122var
123123 sei: TShellExecuteInfoA;
124124begin
125125 FillChar(sei, SizeOf(sei), 0);
126126 sei.cbSize := SizeOf(sei);
127127 sei.Wnd := Handle;
128128 sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
129129 sei.lpVerb := 'runas';
130130 sei.lpFile := PAnsiChar(Path);
131131 sei.lpParameters := PAnsiChar(Params);
132132 sei.nShow := SW_SHOWNORMAL;
133133 Result := ShellExecuteExA(@sei);
134134end;
135135
136136procedure TFormMain.RunAddOrRemoveApplication;
137137begin
138138 // Example that uses elevated rundll to open the Control Panel to Programs and features
139139 RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');
140140end;
141141```
142142
143143## Unix fpsystem, fpexecve と shell
144144
145145これらの関数はプラットフォーム依存です。
146146
147147- [fpsystem reference](http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html)
148148- [fpexecve reference](http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html)
149149- [shell reference](http://www.freepascal.org/docs-html/rtl/unix/shell.html)
150150
1511511.0.x では **Unix.Shell** は非推奨になっています。fpsystem をお使いください。
152152
153153## TProcess
154154
155155TProcessを使用して外部プログラムを実行することができます。TProcessを使うメリットは次の通りです。
156156
157157- プラットホームに依存しない
158158- 標準入力からの読み込み、標準出力への書き込みが可能
159159- コマンドの終了を待つことも、待たずに実行を続けることもできる
160160
161161重要な注意点:
162162
163163- TProcess はターミナルでもシェルでもありません。直接スクリプトを実行することや、 "\|"、"\>"、"\<"、"&" などを使って出力結果をリダイレクトすることはできません。 しかし、 TProcess を用いて同様な結果を得ることができます。いくつかのサンプルを下に示します。
164164- おそらく Linux/Unix では、実行ファイルのフルパスを指定する必要があります。例えば 'cp' ではなく '/bin/cp' を使います。実行ファイルの存在するディレクトリが標準的な PATH 環境変数に含まれるならば、LCL の [[doc:lcl/fileutil/index.html|FileUtil]] にある [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] 関数が使えます。
165165- Windows では、コマンドに PATH が通っていれば、フルパスを指定する必要はありません。
166166- [[doc:fcl/process/tprocess.html|TProcess reference]]
167167
168168### 単純な見本
169169
170170``` pascal
171171 // これはどのようにして外部プログラムを実行するかを示すデモプログラムです
172172 program launchprogram;
173173
174174 // 関数や手続きを使用するためにファイルをインクルードします
175175 uses
176176 Classes, SysUtils, Process;
177177
178178 // "TProcess"型の変数"AProcess"を定義します
179179 var
180180 AProcess: TProcess;
181181
182182 // ここからプログラムが開始します
183183 begin
184184 // TProcessオブジェクトを生成し、変数AProcessにアサインします
185185 AProcess := TProcess.Create(nil);
186186
187187 // AProcessに実行するコマンドを伝えます
188188 // FreePascalコンパイラを実行してみましょう
189189 AProcess.Executable:= 'ppc386';
190190 // ppc386に渡すコマンドライン引数を与えます (というわけで ppc386 -h を実行することになります):
191191 AProcess.Parameters.Add('-h');
192192 // .Executable と .Parameters は以前の版の FPC にはなく、
193193 // .CommandLine プロパティを用いていました。これはまだ残っていますが、旧式です。
194194 // 使わないでください。
195195
196196
197197 // プログラムを走らせるときの、オプションを定義しましょう
198198 // このオプションは、実行した外部プログラムが停止するまで、
199199 // このプログラムが動かないようにします vvvvvvvvvvvvvv
200200 AProcess.Options := AProcess.Options + [poWaitOnExit];
201201
202202 // AProcessにコマンドを実行させましょう
203203 AProcess.Execute;
204204
205205 // ppc386が停止するまで、これは実行されません。
206206 AProcess.Free;
207207 end.
208208```
209209
210210おめでとう!自分のプログラムから外部プログラムを実行する方法を学べました。
211211
212212### より向上した(でもまだ正しくない)見本
213213
214214それでは、どのようにして実行した外部プログラムの出力を読めばよいでしょう? 先ほどの例を拡張してみましょう。
215215
216216**この例は学びやすいように簡単に書かれています。しかし、最終製品のコードをこう書いてはいけません。かわりに #巨大な出力を読み取る のコードを使います。**
217217
218218``` pascal
219219 // これはどのようにして外部プログラムを実行させ、その出力を読むかを
220220 // 示す(問題のある)デモプログラムです。
221221 program launchprogram;
222222
223223 // 関数や手続きを使用するためにファイルをインクルードします
224224 uses
225225 Classes, SysUtils, Process;
226226
227227 // "TProcess"型の変数"AProcess"を定義します
228228 // また、プログラムの出力からデータを読み取り、
229229 // 保存する TStringList を追加します。
230230 var
231231 AProcess: TProcess;
232232 AStringList: TStringList;
233233
234234 // ここからプログラムが開始します
235235 begin
236236 // TProcessオブジェクトを生成し、変数AProcessにアサインします
237237 AProcess := TProcess.Create(nil);
238238
239239 // TStringListオブジェクトを生成します
240240 AStringList := TStringList.Create;
241241
242242 // AProcessに実行するコマンドを伝えます
243243 // FreePascalコンパイラを実行してみましょう
244244 AProcess.CommandLine := 'ppc386 -h';
245245
246246 // プログラムを走らせるときの、オプションを定義しましょう
247247 // [poWaitOnExit]は、実行した外部プログラムが停止するまで、
248248 // このプログラムが動かないようにします
249249 // また、[poUsePipes]でファイルの出力を読みたいことを伝えます
250250 AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
251251
252252 // AProcessは実行するコマンドラインを知っています
253253 AProcess.Execute;
254254
255255 // ppc386が停止するまで、次にいきません
256256
257257 // プログラムの出力を、TStringListに読み込みます。
258258 AStringList.LoadFromStream(AProcess.Output);
259259
260260 // ファイルに保存します
261261 AStringList.SaveToFile('output.txt');
262262
263263 // ファイルが保存されたので、
264264 // TStringListとTProcessを開放します
265265 AStringList.Free;
266266 AProcess.Free;
267267 end.
268268```
269269
270270### 巨大な出力を読み取る
271271
272272前述したサンプルでは、外部プログラムの終了まで待ち、その後に外部プログラムの出力内容を読み取りました。
273273
274274ここで、外部プログラムが大量のデータを出力すると想定した場合、パイプが満杯になるとパイプの内容が読み出されるまで外部プログラムはストップします。しかし、外部プログラムが終わるまでは、読み出し手のプログラムはパイプの内容を読み出せないため、袋小路に陥ります。
275275
276276そこで、次のサンプルでは poWaitOnExitを使わず、プログラムの実行中に出力から読むようにしました。出力はメモリストリームに保存され、追って TStringList に読み出されます。
277277
278278``` pascal
279279program procoutlarge;
280280{
281281 Copyright (c) 2004-2011 by Marc Weustink and contributors
282282
283283 This example is created in the hope that it will be useful,
284284 but WITHOUT ANY WARRANTY; without even the implied warranty of
285285 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
286286}
287287// これはどのようにして外部プログラムを実行させ、その出力を読むかを
288288// 示す(実用的な)デモプログラムです。
289289// Memorystream が一時的な出力バッファとして使われます
290290uses
291291 Classes, Process, SysUtils;
292292
293293const
294294 READ_BYTES = 2048;
295295
296296var
297297 OurCommand: String;
298298 OutputLines: TStringList;
299299 MemStream: TMemoryStream;
300300 OurProcess: TProcess;
301301 NumBytes: LongInt;
302302 BytesRead: LongInt;
303303
304304begin
305305 // 一時的な Memorystream が出力バッファとして使われます
306306 MemStream := TMemoryStream.Create;
307307 BytesRead := 0;
308308
309309 OurProcess := TProcess.Create(nil);
310310 // ディレクトリの再帰的読み出しは好例です。
311311 OurCommand:='invalid command, please fix the IFDEFS.';
312312 {$IFDEF Windows}
313313 //dir は CMD.EXE の内部コマンドなので直接使えません。
314314 //そこでシェルを使います:
315315 OurCommand:='cmd.exe /c "dir /s c:\windows\"';
316316 {$ENDIF Windows}
317317 {$IFDEF Unix}
318318 //LinuxやUnixてテストしてください
319319 OurCommand := 'ls --recursive --all -l /';
320320 {$ENDIF Unix}
321321 writeln('-- Going to run: ' + OurCommand);
322322 OurProcess.CommandLine := OurCommand;
323323
324324 // 出力サイズが不明であり、poWaintOnExit は使用できません。
325325 // Linux では出力パイプの大きさは2KBであり、
326326 // データの大きさがこれ以上である場合、デッドロックに乗り上げる
327327 // ことになります。
328328 OurProcess.Options := [poUsePipes];
329329 WriteLn('-- External program run started');
330330 OurProcess.Execute;
331331 while True do
332332 begin
333333 // メモリ空間を確保します
334334 MemStream.SetSize(BytesRead + READ_BYTES);
335335
336336 // 読んでいきます
337337 NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
338338 if NumBytes > 0 // All read() calls will block, except the final one.
339339 then begin
340340 Inc(BytesRead, NumBytes);
341341 Write('.') //スクリーンに進行状態を表示します
342342 end else
343343 BREAK // プログラムの実行が終了します
344344 end;
345345 if BytesRead > 0 then WriteLn;
346346 MemStream.SetSize(BytesRead);
347347 WriteLn('-- External program run complete');
348348
349349 OutputLines := TStringList.Create;
350350 OutputLines.LoadFromStream(MemStream);
351351 WriteLn('-- External program output line count = ', OutputLines.Count, ' --');
352352 for NumBytes := 0 to OutputLines.Count - 1 do
353353 begin
354354 WriteLn(OutputLines[NumBytes]);
355355 end;
356356 WriteLn('-- Program end');
357357 OutputLines.Free;
358358 OurProcess.Free;
359359 MemStream.Free;
360360end.
361361```
362362
363363### TProcessの出力と入力を使用する
364364
365365[Lazarus-CCR SVN](https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process)のprocessdemoを参照してください。
366366
367367### TProcess利用のヒント
368368
369369クロスプラットフォームのアプリケーションを作成する場合、"{\$IFDEF}s" と "{\$ENDIF}s" 命令を利用することで、OSに応じてコマンドラインを変更できます。
370370
371371例:
372372
373373``` pascal
374374 {...}
375375 AProcess:TProcess.Create(nil)
376376 {$IFDEF WIN32}
377377 AProcess.CommandLine := 'calc.exe'; //Windows Calc
378378 {$ENDIF}
379379 {$IFDEF LINUX}
380380 AProcess.CommandLine := 'kcalc'; //KDE Calc
381381 {$ENDIF}
382382 AProcess.Execute; //右の書き方もできます。 AProcess.Active:=True
383383 {...}
384384```
385385
386386### OS X のアプリケーションバンドルを前面に出す
387387
388388**アプリケーションバンドル** は、バンドル内の実行ファイルを指定することで TProcess から起動することができます。たとえば:
389389
390-`AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';`
390+``` pascal
391+AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';
392+```
391393
392394これで、「カレンダー」が起動します。しかし、ウインドゥは現在使用中のアプリケーションの後ろ側にいってしまいます。アプリケーションを前面に出すには、**open** ユーティリティを引数 **-n** 付きで用います。
393395
394-`AProcess.Executable:='/usr/bin/open';`\
395-`AProcess.Parameters.Add('-n');`\
396-`AProcess.Parameters.Add('/Application/iCal.app');`
396+``` pascal
397+AProcess.Executable:='/usr/bin/open';
398+AProcess.Parameters.Add('-n');
399+AProcess.Parameters.Add('/Application/iCal.app');
400+```
397401
398402アプリケーションが引数を要求する場合、**open** に、**--arg** 引数を渡すことができます。アプリケーションに渡す引数はその後に書きます:
399403
400-`AProcess.Parameters.Add('--args');`\
401-`AProcess.Parameters.Add('argument1');`\
402-`AProcess.Parameters.Add('argument2');`
404+```text
405+AProcess.Parameters.Add('--args');
406+AProcess.Parameters.Add('argument1');
407+AProcess.Parameters.Add('argument2');
408+```
403409
404410### aspell processによるプロセス間通信(talking)のサンプル
405411
406412[pasdoc](http://pasdoc.sourceforge.net/)のソースコードを見ると、パイプを介して実行中のaspellプロセスと通信することでスペルチェックを行う二つのユニットがあります:
407413
408414- [PasDoc_ProcessLineTalk.pas unit](http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas) は、TProcessを継承するTProcessLineTalkクラスを実装しています。 このクラスによって、どんなプロセスとの間でも行単位の通信が容易にできます。
409415
410416- [PasDoc_Aspell.pas units](http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas) は、TAspellProcessクラスを実装しています。 このクラスは、基礎となるTProcessLineTalkのインスタンスをaspellプロセスの実行と実行中のaspellプロセスとの通信に用いながら、スペルチェックを行います。
411417
412418両ユニットは pasdoc の残りのソースから比較的独立しています。 パイプを介して他のプログラムを実行し、プロセスと通信するためにTPorcess を用いる実例として役に立つでしょう。
413419
414420### "\| \< \>" みたいなシェルの演算子を置き換える
415421
416422もっと複雑なコマンド、つまり、パイプやリダイレクトによって他のコマンドやファイルにデータを流し込むコマンドを実行させたい場合があります。例えば、
417423
418424``` pascal
419425ShellExecute('firstcommand.exe | secondcommand.exe');
420426```
421427
422428あるいは
423429
424430``` pascal
425431ShellExecute('dir > output.txt');
426432```
427433
428434TProcess でこれを実行しても上手く行きません。つまり、
429435
430436``` pascal
431437// 動作しない
432438Process.CommandLine := 'firstcommand.exe | secondcommand.exe';
433439Process.Execute;
434440```
435441
436442#### リダイレクトのための特別な演算子が働かない理由
437443
438444TProcess はシェル環境ではなくて、たんなる一つのプロセスです。二つではなく、たった一つの。しかし、期待通りに出力をリダイレクトすることができます。次の #TProcess で出力をリダイレクトする方法 をご覧下さい。
439445
440446### TProcess で出力をリダイレクトする方法 
441447
442448TProcess のインスタンスを、コマンド**毎に**用意すれば、コマンドの出力を別のコマンドに送ることができます。
443449
444450ここではあるプロセスの出力を他のプロセスにリダイレクトする方法を示します。あるプロセスの出力をファイルにリダイレクトする方法は、#巨大な出力を読み取るの例をご覧下さい。
445451
446452poStderrToOutPut オプションを指定すれば、標準出力(stdout)だけでなく、標準エラー出力(stderr)のリダイレクトも可能です。この例で、二つ目のプロセスがこのオプションを使っています。
447453
448454``` pascal
449455program Project1;
450456
451457uses
452458 Classes, sysutils, process;
453459
454460var
455461 FirstProcess,
456462 SecondProcess: TProcess;
457463 Buffer: array[0..127] of char;
458464 ReadCount: Integer;
459465 ReadSize: Integer;
460466begin
461467 FirstProcess := TProcess.Create(nil);
462468 SecondProcess := TProcess.Create(nil);
463469
464470 FirstProcess.Options := [poUsePipes];
465471 SecondProcess.Options := [poUsePipes,poStderrToOutPut];
466472
467473 FirstProcess.CommandLine := 'pwd';
468474 SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -';
469475 // これで "pwd | grep / -" と同等になるはずです
470476
471477 FirstProcess.Execute;
472478 SecondProcess.Execute;
473479
474480 while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
475481 begin
476482 if FirstProcess.Output.NumBytesAvailable > 0 then
477483 begin
478484 // 読み込むデータ量がバッファの大きさを超えないようにする
479485 ReadSize := FirstProcess.Output.NumBytesAvailable;
480486 if ReadSize > SizeOf(Buffer) then
481487 ReadSize := SizeOf(Buffer);
482488 // 出力をバッファに読み込む
483489 ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
484490 // バッファの中身を第二のプロセスに書き込む
485491 SecondProcess.Input.Write(Buffer[0], ReadCount);
486492
487493 // 第二のプロセスが出力するデータが多過ぎるとデッドロックを起こします。
488494 // その前にデータを読まなければなりません。
489495 // 「巨大な出力を読み取る」の例をご覧下さい。
490496 end;
491497 end;
492498 // 第二のプロセスの入力を閉じます。
493499 // これで第二のプロセスの処理が終わります。
494500 SecondProcess.CloseInput;
495501
496502 // 終了を待ちます。
497503 // どんなコマンドを実行するか良く注意すべきです。
498504 // 入力を閉じても終了しないコマンドだと、
499505 // 次の行が無限ループに突入します。
500506 while SecondProcess.Running do
501507 Sleep(1);
502508 // こんな感じ。ここから先も少し役立ちますよ。
503509
504510 // バッファを第二プロセスの出力用に再利用します。
505511 // 出力はこのプログラムの標準出力に送られます。
506512 WriteLn('Grep output Start:');
507513 ReadSize := SecondProcess.Output.NumBytesAvailable;
508514 if ReadSize > SizeOf(Buffer) then
509515 ReadSize := SizeOf(Buffer);
510516 if ReadSize > 0 then
511517 begin
512518 ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);
513519 WriteLn(Copy(Buffer,0, ReadCount));
514520 end
515521 else
516522 WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);
517523 WriteLn('Grep output Finish:');
518524
519525 // プロセスオブジェクトを解放します
520526 FirstProcess.Free;
521527 SecondProcess.Free;
522528end.
523529```
524530
525531はい、これであるプログラムの出力を別のプログラムやファイルに流し込むことができました。
526532
527533#### 注
528534
529535この例は、「やりすぎ」に見えるかもしれません。というのも、TProcess からシェルを起動してやれば「複雑な」コマンドも実行できるからです。こんなふうに:
530536
531537``` pascal
532538Process.Commandline := 'sh -c "pwd | grep / -"';
533539```
534540
535541しかし、上記の例はもっとクロスプラットフォームです。Windows でも Linux でもそのまま走ります。"sh" コマンドがあろうがなかろうが構いません(UNIX系のシステムにしかないのが普通です)。また、この例はもっと柔軟です。各プロセス独立にそれぞれの標準入力、標準出力、標準エラー出力を読み書きすることができるからです。プロジェクトの中で、これが利点となることがあるでしょう。
536542
537543### 入出力のリダイレクトと root 権限での実行
538544
539545UNIXやOS Xでいつも問題になるのが、プログラムを root 権限で(あるいはもっと一般的に、他のユーザアカウントで)実行したい場合です。*ping* コマンドを走らせるのが実例になるでしょう。
540546
541547If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([1](http://lazarus.freepascal.org/index.php/topic,14479.0.html)). This sample runs `ls` on the `/root` directory, but can of course be adapted.
542548
543549A **better way** to do this is to use the policykit package, which should be available on all recent Linuxes. [See the forum thread for details.](http://lazarus.freepascal.org/index.php/topic,14479.0.html)
544550
545551Large parts of this code are similar to the earlier example, but it also shows how to redirect stdout and stderr of the process being called separately to stdout and stderr of our own code.
546552
547553``` pascal
548554program rootls;
549555
550556{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,
551557calling sudo on Linux/macOS, and supplying input on stdin}
552558{$mode objfpc}{$H+}
553559
554560uses
555561 Classes,
556562 Math, {for min}
557563 Process;
558564
559565 procedure RunsLsRoot;
560566 var
561567 Proc: TProcess;
562568 CharBuffer: array [0..511] of char;
563569 ReadCount: integer;
564570 ExitCode: integer;
565571 SudoPassword: string;
566572 begin
567573 WriteLn('Please enter the sudo password:');
568574 Readln(SudoPassword);
569575 ExitCode := -1; //Start out with failure, let's see later if it works
570576 Proc := TProcess.Create(nil); //Create a new process
571577 try
572578 Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr
573579 Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo
574580 // -S causes sudo to read the password from stdin.
575581 Proc.Execute; //start it. sudo will now probably ask for a password
576582
577583 // write the password to stdin of the sudo program:
578584 SudoPassword := SudoPassword + LineEnding;
579585 Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
580586 SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof
581587 SudoPassword := ''; // and make the program a bit safer from snooping?!?
582588
583589 // main loop to read output from stdout and stderr of sudo
584590 while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
585591 (Proc.Stderr.NumBytesAvailable > 0) do
586592 begin
587593 // read stdout and write to our stdout
588594 while Proc.Output.NumBytesAvailable > 0 do
589595 begin
590596 ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more
591597 Proc.Output.Read(CharBuffer, ReadCount);
592598 Write(StdOut, Copy(CharBuffer, 0, ReadCount));
593599 end;
594600 // read stderr and write to our stderr
595601 while Proc.Stderr.NumBytesAvailable > 0 do
596602 begin
597603 ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more
598604 Proc.Stderr.Read(CharBuffer, ReadCount);
599605 Write(StdErr, Copy(CharBuffer, 0, ReadCount));
600606 end;
601607 end;
602608 ExitCode := Proc.ExitStatus;
603609 finally
604610 Proc.Free;
605611 Halt(ExitCode);
606612 end;
607613 end;
608614
609615begin
610616 RunsLsRoot;
611617end.
612618```
613619
614620Other thoughts: It would no doubt be advisable to see if sudo actually prompts for a password. This can be checked consistently by setting the environment variable SUDO_PROMPT to something we watch for while reading the stdout of TProcess avoiding the problem of the prompt being different for different locales. Setting an environment variable causes the default values to be cleared(inherited from our process) so we have to copy the environment from our program if needed.
615621
616622### Using fdisk with sudo on Linux
617623
618624The following example shows how to run fdisk on a Linux machine using the sudo command to get root permissions.
619625
620626``` pascal
621627program getpartitioninfo;
622628{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc.
623629Modified from Lazarus snippet to FPC program for ease of understanding/conciseness by BigChimp}
624630
625631Uses
626632 Classes, SysUtils, FileUtil, Process;
627633
628634var
629635 hprocess: TProcess;
630636 sPass: String;
631637 OutputLines: TStringList;
632638
633639begin
634640 sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password
635641 OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure
636642 // OutputLines is freed... Same for hProcess.
637643
638644 // The following example will open fdisk in the background and give us partition information
639645 // Since fdisk requires elevated priviledges we need to
640646 // pass our password as a parameter to sudo using the -S
641647 // option, so it will will wait till our program sends our password to the sudo application
642648 hProcess := TProcess.Create(nil);
643649 // On Linux/Unix/macOS, we need specify full path to our executable:
644650 hProcess.Executable := '/bin/sh';
645651 // Now we add all the parameters on the command line:
646652 hprocess.Parameters.Add('-c');
647653 // Here we pipe the password to the sudo command which then executes fdisk -l:
648654 hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');
649655 // Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe
650656 hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
651657 // Now run:
652658 hProcess.Execute;
653659
654660 // hProcess should have now run the external executable (because we use poWaitOnExit).
655661 // Now you can process the process output (standard output and standard error), eg:
656662 OutputLines.Add('stdout:');
657663 OutputLines.LoadFromStream(hprocess.Output);
658664 OutputLines.Add('stderr:');
659665 OutputLines.LoadFromStream(hProcess.Stderr);
660666 // Show output on screen:
661667 writeln(OutputLines.Text);
662668
663669 // Clean up to avoid memory leaks:
664670 hProcess.Free;
665671 OutputLines.Free;
666672
667673 //Below are some examples as you see we can pass illegal characters just as if done from terminal
668674 //Even though you have read elsewhere that you can not I assure with this method you can :)
669675
670676 //hprocess.Parameters.Add('ping -c 1 www.google.com');
671677 //hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');
672678
673679 //Using QuotedStr() is not a requirement though it makes for cleaner code;
674680 //you can use double quote and have the same effect.
675681
676682 //hprocess.Parameters.Add('glxinfo | grep direct');
677683
678684 // This method can also be used for installing applications from your repository:
679685
680686 //hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name');
681687
682688 end.
683689```
684690
685691### 空白を含む引数 (シェルの引用符を置き換える)
686692
687693Linuxのシェルでは、引数を引用符でくるむことができます。たとえばこのように:
688694
689-`gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram`
695+```text
696+gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram
697+```
690698
691699このとき、GDB が受け取る引数の数は 3 です(その他に、第0の引数として、実行ファイルのフルパスが渡されます):
692700
6937011. --batch
6947022. --eval-command=info symbol 0x0000DDDD
6957033. the full path to myprogram
696704
697705TProcess は空白を含む引数を渡せますが、引数の一部を囲むのではなく、全体を囲みます。このように:
698706
699-`TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';`
707+``` pascal
708+TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';
709+```
700710
701711フルパスしか渡せないことを思い出してください。
702712
703713これに関連して、次の議論をお読みください: <http://bugs.freepascal.org/view.php?id=14446>
704714
705715## (Process.)RunCommand
706716
707717FPC 2.6.2 では、Process ユニットに、TProcess のヘルパ関数が加えられました。これは [[Projects using Lazarus|fpcup]] プロジェクトで用いられた TProcess のラッパをもとにしています。 これらの関数は、基礎から中級クラスの用法を想定しており、一行から、大量の出力行までサポートしています。
708718
709719以下、簡単な用例です
710720
711721``` pascal
712722uses Process;
713723...
714724var s : ansistring;
715725...
716726if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
717727 writeln(s);
718728```
719729
720730オーヴァロードした新たな RunCommand はプログラムの終了コードを返します。RunCommandInDir は、コマンドを p.CurrentDirectory で決めた他のディレクトリで実行します:
721731
722732``` pascal
723733function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;
724734function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;
725735function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;
726736```
727737
728738## LCLIntf による別法
729739
730740特定の外部プログラムを明示的に呼ぶ必要がなく、ある文書を開くためになんらかのアプリケーションが動けばいいという場合があります。どのアプリケーションでその文書を開くかは OS まかせで、文書のタイプに合わせたデフォルトのアプリケーションが用いられます。次にいくつか例を挙げます。
731741
732742### デフォルトのアプリケーションで文書を開く
733743
734744特定のプログラムを実行したいというより、ある文書/ファイルを、関連づけられたデフォルトのアプリケーションで開きたい場合があります。 これは OS に依存します。Lazarus ではこのような場合のためにプラットフォームに依存しない手続き、**OpenDocument** を用意しています。呼出し側はブロックされずそのまま実行を続けます。
735745
736746``` pascal
737747uses LCLIntf;
738748...
739749OpenDocument('manual.pdf');
740750...
741751```
742752
743753- [[opendocument|OpenDocument reference]]
744754
745755### デフォルトのWebブラウザでWebページを開く
746756
747757OpenURL に開きたい URL を渡すだけです。場合によっては、最初の <http://> は不要です。ファイル名を渡すと OpenDocument() と同じ結果が得られます。
748758
749759``` pascal
750760uses LCLIntf;
751761...
752762OpenURL('www.lazarus.freepascal.org/');
753763```
754764
755765関連項目:
756766
757767- [[OpenURL|OpenURL reference]]
758768
759769**TProcess** をこんなふうに使ってもいけます:
760770
761771``` pascal
762772uses Process;
763773
764774procedure OpenWebPage(URL: string);
765775// URL は "" でかこってください。例えば、"www.lazarus.freepascal.org"
766776var
767777 Browser, Params: string;
768778begin
769779 FindDefaultBrowser(Browser, Params);
770780 with TProcess.Create(nil) do
771781 try
772782 Executable := Browser;
773783 Params:=Format(Params, [URL]);
774784 Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself
775785 Parameters.Add(Params);
776786 Options := [poNoConsole];
777787 Execute;
778788 finally
779789 Free;
780790 end;
781791end;
782792```
783793
784794## 関連項目
785795
786796- [TProcess documentation](http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html)
787797- [[opendocument]]
788798- [[OpenURL]]
789799- [[TXMLPropStorage]]
790800- [[Webbrowser]]
791801
792802---
793803*From the Free Pascal wiki, page "Executing External Programs/ja", as it stood on 2021-04-01 after a change by Trev, 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).*

Back to history