Difference between revisions of "Executing External Programs/ja"

From Free Pascal wiki
Jump to navigationJump to search
m (OSX -> macOS)
m (Fix typos)
 
(One intermediate revision by the same user not shown)
Line 104: Line 104:
 
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.
 
The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.
  
If 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).
+
If 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).
  
 
=== Using ShellExecuteEx for elevation/administrator permissions ===
 
=== Using ShellExecuteEx for elevation/administrator permissions ===
Line 724: Line 724:
  
 
関連項目:
 
関連項目:
* [[openurl|OpenURL reference]]
+
* [[OpenURL|OpenURL reference]]
  
 
'''TProcess''' をこんなふうに使ってもいけます:
 
'''TProcess''' をこんなふうに使ってもいけます:
Line 753: Line 753:
 
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]
 
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]
 
* [[opendocument]]
 
* [[opendocument]]
* [[openurl]]
+
* [[OpenURL]]
 
* [[TXMLPropStorage]]
 
* [[TXMLPropStorage]]
 
* [[Webbrowser]]
 
* [[Webbrowser]]

Latest revision as of 04:30, 1 April 2021

Deutsch (de) English (en) español (es) français (fr) italiano (it) 日本語 (ja) Nederlands (nl) polski (pl) português (pt) русский (ru) slovenčina (sk) 中文(中国大陆)‎ (zh_CN)

日本語版メニュー
メインページ - Lazarus Documentation日本語版 - 翻訳ノート - 日本語障害情報

概要

外部のプログラムを動かすにはいくつかの方法がありますが、このチュートリアルでは TProcessを中心に紹介しています。

Delphiで ShellExecute や WinExec を使っていたなら、FPC/Lazarusにおける代替としてTProcessを使用するようにしましょう。(TProcess は cross-platformであり、Linuxでも有効です。)

Note: FPC/Lazarus は ShellExecute 及び WinExec をサポートしていますが、win32のみで有効です。 あなたのプログラムが cross-platformを目標にしているなら、TProcessの使用が最善です! 次に、さまざまな手法の比較を表にします

方法 プラットフォーム 一行で書けるか 機能の程度
SysUtils.ExecuteProcess クロスプラットフォーム Yes 極めて限定的、同期的
(ShellApi) ShellExecute MS Windows のみ Yes 多数。より高位の/管理者権限で実行可能
Unix fpsystem, fpexecve Unix のみ
TProcess クロスプラットフォーム No 全機能が可能
RunCommand(InDir) クロスプラットフォーム FPC 2.6.2以降が必要 Yes よくあるTProcess利用法をカバー
(LCLIntf) OpenDocument クロスプラットフォーム Yes 文書を開くだけ

SysUtils.ExecuteProcess

(クロスプラットフォーム)
最も簡単な方法は、SysUtils.ExecuteProcessを使用することです。ただし、モーダル(訳注: 外部プログラムに制御が移ること。下記)ですし、パイプもいかなるコントロールも使えません:

SysUtils.ExecuteProcess(UTF8ToSys('実行ファイルへのフルパス'), '', [])

呼出しは同期的です: 呼び出した側は、外部プログラムの動作が終了するまで「ハング」します - しかしアプリケーションの実行を進める前に、ユーザになにかをさせるには便利かもしれません。次の節の TProcess はもっと広い用途に用いることができ、おすすめです。Windows だけでいいなら、ShellExecute も使えます。

MS Windows : CreateProcess, ShellExecute と WinExec

Light bulb  Note: FPC/Lazarus は CreateProcessShellExecuteWinExec をサポートしてはいますが、プラットフォームは Win32/64 に限定されます。クロスプラットフォームなプログラムには TProcess をお使いください。
Light bulb  Note: WinExec は 16 bit 呼出しで、何年も前から Windows API の中で旧式化しています。最近の FPC は警告を発します。

ShellExecute は MS Windows で標準的な関数です (ShellApi.h)。マイクロソフトによる解説は documentation on MSDN をどうぞ。この関数が不安定だと思う方は、この解説の中の COM の初期化についての注釈をお読み下さい。

uses ..., ShellApi;

// 簡単な一行野郎(エラーを無視) :
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;

// バッチファイルを実行 :
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;

// 任意のフォルダでコマンドプロンプトを開く :
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;

// (隠れたコマンドウィンドウを通して)start コマンドを使ってデフォルトブラウザでウェブページの URL を開く:
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;

// あるいは便利な手続:
procedure RunShellExecute(const prog,params:string);
begin
  //  ( ハンドル, nil/'open'/'edit'/'find'/'explore'/'print',   // 'open' は必ずしも必要ではない 
  //      パス付きプログラム名, 引数, 作業用フォルダ,
  //        0=隠し / 1=SW_SHOWNORMAL / 3=最大化 / 7=最小化)   //定数 SW_ は Windows ユニットにある
  if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //成功
  // 0..32 はエラー
end;

WideChar 版の ShellExecuteExW や AnsiChar 版の ShellExecuteExA もあります。

The fMask option can also use SEE_MASK_DOENVSUBST or SEE_MASK_FLAG_NO_UI or SEE_MASK_NOCLOSEPROCESS, etc.

If 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).

Using ShellExecuteEx for elevation/administrator permissions

If you need to execute external program with administrator/elevated privileges, you can use the runas method with the alternative ShellExecuteEx function:

uses ShellApi, ...;

function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;
var
  sei: TShellExecuteInfoA;
begin
  FillChar(sei, SizeOf(sei), 0);
  sei.cbSize := SizeOf(sei);
  sei.Wnd := Handle;
  sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
  sei.lpVerb := 'runas';
  sei.lpFile := PAnsiChar(Path);
  sei.lpParameters := PAnsiChar(Params);
  sei.nShow := SW_SHOWNORMAL;
  Result := ShellExecuteExA(@sei);
end;

procedure TFormMain.RunAddOrRemoveApplication;
begin
  // Example that uses elevated rundll to open the Control Panel to Programs and features
  RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');
end;

Unix fpsystem, fpexecve と shell

これらの関数はプラットフォーム依存です。

1.0.x では Unix.Shell は非推奨になっています。fpsystem をお使いください。

TProcess

TProcessを使用して外部プログラムを実行することができます。TProcessを使うメリットは次の通りです。

  • プラットホームに依存しない
  • 標準入力からの読み込み、標準出力への書き込みが可能
  • コマンドの終了を待つことも、待たずに実行を続けることもできる

重要な注意点:

  • TProcess はターミナルでもシェルでもありません。直接スクリプトを実行することや、 "|"、">"、"<"、"&" などを使って出力結果をリダイレクトすることはできません。 しかし、 TProcess を用いて同様な結果を得ることができます。いくつかのサンプルを下に示します。
  • おそらく Linux/Unix では、実行ファイルのフルパスを指定する必要があります。例えば 'cp' ではなく '/bin/cp' を使います。実行ファイルの存在するディレクトリが標準的な PATH 環境変数に含まれるならば、LCL の FileUtil にある FindDefaultExecutablePath 関数が使えます。
  • Windows では、コマンドに PATH が通っていれば、フルパスを指定する必要はありません。
  • TProcess reference

単純な見本

 // これはどのようにして外部プログラムを実行するかを示すデモプログラムです
 program launchprogram;
 
 // 関数や手続きを使用するためにファイルをインクルードします
 uses 
   Classes, SysUtils, Process;
 
 // "TProcess"型の変数"AProcess"を定義します
 var 
   AProcess: TProcess;
 
 // ここからプログラムが開始します
 begin
  // TProcessオブジェクトを生成し、変数AProcessにアサインします
  AProcess := TProcess.Create(nil);
 
  // AProcessに実行するコマンドを伝えます
  // FreePascalコンパイラを実行してみましょう
  AProcess.Executable:= 'ppc386';
  // ppc386に渡すコマンドライン引数を与えます (というわけで ppc386 -h を実行することになります):
  AProcess.Parameters.Add('-h');
  // .Executable と .Parameters は以前の版の FPC にはなく、
  // .CommandLine プロパティを用いていました。これはまだ残っていますが、旧式です。
  // 使わないでください。
 

  // プログラムを走らせるときの、オプションを定義しましょう
  // このオプションは、実行した外部プログラムが停止するまで、
  // このプログラムが動かないようにします           vvvvvvvvvvvvvv
   AProcess.Options := AProcess.Options + [poWaitOnExit];
 
  // AProcessにコマンドを実行させましょう
   AProcess.Execute;
 
  // ppc386が停止するまで、これは実行されません。
   AProcess.Free;   
 end.

おめでとう!自分のプログラムから外部プログラムを実行する方法を学べました。

より向上した(でもまだ正しくない)見本

それでは、どのようにして実行した外部プログラムの出力を読めばよいでしょう? 先ほどの例を拡張してみましょう。

この例は学びやすいように簡単に書かれています。しかし、最終製品のコードをこう書いてはいけません。かわりに #巨大な出力を読み取る のコードを使います。

 // これはどのようにして外部プログラムを実行させ、その出力を読むかを
 // 示す(問題のある)デモプログラムです。
 program launchprogram;
 
 // 関数や手続きを使用するためにファイルをインクルードします
 uses 
   Classes, SysUtils, Process;
 
 // "TProcess"型の変数"AProcess"を定義します
 // また、プログラムの出力からデータを読み取り、
 // 保存する TStringList を追加します。
 var 
   AProcess: TProcess;
   AStringList: TStringList;
 
 // ここからプログラムが開始します
 begin
   // TProcessオブジェクトを生成し、変数AProcessにアサインします
   AProcess := TProcess.Create(nil);
 
   // TStringListオブジェクトを生成します
   AStringList := TStringList.Create;
 
   // AProcessに実行するコマンドを伝えます
   // FreePascalコンパイラを実行してみましょう
   AProcess.CommandLine := 'ppc386 -h';
 
   // プログラムを走らせるときの、オプションを定義しましょう
   // [poWaitOnExit]は、実行した外部プログラムが停止するまで、
   // このプログラムが動かないようにします
   // また、[poUsePipes]でファイルの出力を読みたいことを伝えます
   AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 
   // AProcessは実行するコマンドラインを知っています
   AProcess.Execute;
   
   // ppc386が停止するまで、次にいきません
 
   // プログラムの出力を、TStringListに読み込みます。
   AStringList.LoadFromStream(AProcess.Output);
   
   // ファイルに保存します
   AStringList.SaveToFile('output.txt');
 
   // ファイルが保存されたので、
   // TStringListとTProcessを開放します
   AStringList.Free;
   AProcess.Free;   
 end.

巨大な出力を読み取る

前述したサンプルでは、外部プログラムの終了まで待ち、その後に外部プログラムの出力内容を読み取りました。

ここで、外部プログラムが大量のデータを出力すると想定した場合、パイプが満杯になるとパイプの内容が読み出されるまで外部プログラムはストップします。しかし、外部プログラムが終わるまでは、読み出し手のプログラムはパイプの内容を読み出せないため、袋小路に陥ります。

そこで、次のサンプルでは poWaitOnExitを使わず、プログラムの実行中に出力から読むようにしました。出力はメモリストリームに保存され、追って TStringList に読み出されます。

program procoutlarge;
{
    Copyright (c) 2004-2011 by Marc Weustink and contributors
 
    This example is created in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
}
// これはどのようにして外部プログラムを実行させ、その出力を読むかを
// 示す(実用的な)デモプログラムです。
// Memorystream が一時的な出力バッファとして使われます
uses
  Classes, Process, SysUtils;
 
const
  READ_BYTES = 2048;
 
var
  OurCommand: String;
  OutputLines: TStringList;
  MemStream: TMemoryStream;
  OurProcess: TProcess;
  NumBytes: LongInt;
  BytesRead: LongInt;
 
begin
  // 一時的な Memorystream が出力バッファとして使われます
  MemStream := TMemoryStream.Create;
  BytesRead := 0;
 
  OurProcess := TProcess.Create(nil);
  // ディレクトリの再帰的読み出しは好例です。
  OurCommand:='invalid command, please fix the IFDEFS.';
  {$IFDEF Windows}
  //dir は CMD.EXE の内部コマンドなので直接使えません。
  //そこでシェルを使います:
  OurCommand:='cmd.exe /c "dir /s c:\windows\"';
  {$ENDIF Windows}
  {$IFDEF Unix}
  //LinuxやUnixてテストしてください
  OurCommand := 'ls --recursive --all -l /';
  {$ENDIF Unix}
  writeln('-- Going to run: ' + OurCommand);
  OurProcess.CommandLine := OurCommand;

  // 出力サイズが不明であり、poWaintOnExit は使用できません。
  // Linux では出力パイプの大きさは2KBであり、
  // データの大きさがこれ以上である場合、デッドロックに乗り上げる
  // ことになります。
  OurProcess.Options := [poUsePipes];
  WriteLn('-- External program run started');
  OurProcess.Execute;
  while True do
  begin          
  // メモリ空間を確保します
    MemStream.SetSize(BytesRead + READ_BYTES);
 
  // 読んでいきます
    NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
    if NumBytes > 0 // All read() calls will block, except the final one.
    then begin
      Inc(BytesRead, NumBytes);
      Write('.') //スクリーンに進行状態を表示します
    end else 
      BREAK // プログラムの実行が終了します
  end;
  if BytesRead > 0 then WriteLn;
  MemStream.SetSize(BytesRead);
  WriteLn('-- External program run complete');
 
  OutputLines := TStringList.Create;
  OutputLines.LoadFromStream(MemStream);
  WriteLn('-- External program output line count = ', OutputLines.Count, ' --');
  for NumBytes := 0 to OutputLines.Count - 1 do
  begin
    WriteLn(OutputLines[NumBytes]);
  end;
  WriteLn('-- Program end');
  OutputLines.Free;
  OurProcess.Free;
  MemStream.Free;
end.

TProcessの出力と入力を使用する

Lazarus-CCR SVNのprocessdemoを参照してください。

TProcess利用のヒント

クロスプラットフォームのアプリケーションを作成する場合、"{$IFDEF}s" と "{$ENDIF}s" 命令を利用することで、OSに応じてコマンドラインを変更できます。

例:

 {...}
   AProcess:TProcess.Create(nil)
   {$IFDEF WIN32}
   AProcess.CommandLine := 'calc.exe'; //Windows Calc
   {$ENDIF}
   {$IFDEF LINUX}
   AProcess.CommandLine := 'kcalc'; //KDE Calc
   {$ENDIF}
   AProcess.Execute; //右の書き方もできます。 AProcess.Active:=True
 {...}

OS X のアプリケーションバンドルを前面に出す

アプリケーションバンドル は、バンドル内の実行ファイルを指定することで TProcess から起動することができます。たとえば:

AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';

これで、「カレンダー」が起動します。しかし、ウインドゥは現在使用中のアプリケーションの後ろ側にいってしまいます。アプリケーションを前面に出すには、open ユーティリティを引数 -n 付きで用います。

AProcess.Executable:='/usr/bin/open';
AProcess.Parameters.Add('-n');
AProcess.Parameters.Add('/Application/iCal.app');

アプリケーションが引数を要求する場合、open に、--arg 引数を渡すことができます。アプリケーションに渡す引数はその後に書きます:

AProcess.Parameters.Add('--args');
AProcess.Parameters.Add('argument1');
AProcess.Parameters.Add('argument2');

aspell processによるプロセス間通信(talking)のサンプル

pasdocのソースコードを見ると、パイプを介して実行中のaspellプロセスと通信することでスペルチェックを行う二つのユニットがあります:

  • PasDoc_ProcessLineTalk.pas unit は、TProcessを継承するTProcessLineTalkクラスを実装しています。 このクラスによって、どんなプロセスとの間でも行単位の通信が容易にできます。
  • PasDoc_Aspell.pas units は、TAspellProcessクラスを実装しています。 このクラスは、基礎となるTProcessLineTalkのインスタンスをaspellプロセスの実行と実行中のaspellプロセスとの通信に用いながら、スペルチェックを行います。

両ユニットは pasdoc の残りのソースから比較的独立しています。 パイプを介して他のプログラムを実行し、プロセスと通信するためにTPorcess を用いる実例として役に立つでしょう。

"| < >" みたいなシェルの演算子を置き換える

もっと複雑なコマンド、つまり、パイプやリダイレクトによって他のコマンドやファイルにデータを流し込むコマンドを実行させたい場合があります。例えば、

ShellExecute('firstcommand.exe | secondcommand.exe');

あるいは

ShellExecute('dir > output.txt');

TProcess でこれを実行しても上手く行きません。つまり、

// 動作しない
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; 
Process.Execute;

リダイレクトのための特別な演算子が働かない理由

TProcess はシェル環境ではなくて、たんなる一つのプロセスです。二つではなく、たった一つの。しかし、期待通りに出力をリダイレクトすることができます。次の #TProcess で出力をリダイレクトする方法 をご覧下さい。

TProcess で出力をリダイレクトする方法 

TProcess のインスタンスを、コマンド毎に用意すれば、コマンドの出力を別のコマンドに送ることができます。

ここではあるプロセスの出力を他のプロセスにリダイレクトする方法を示します。あるプロセスの出力をファイルにリダイレクトする方法は、#巨大な出力を読み取るの例をご覧下さい。

poStderrToOutPut オプションを指定すれば、標準出力(stdout)だけでなく、標準エラー出力(stderr)のリダイレクトも可能です。この例で、二つ目のプロセスがこのオプションを使っています。

program Project1;
  
uses
  Classes, sysutils, process;
  
var
  FirstProcess,
  SecondProcess: TProcess;
  Buffer: array[0..127] of char;
  ReadCount: Integer;
  ReadSize: Integer;
begin
  FirstProcess  := TProcess.Create(nil);
  SecondProcess := TProcess.Create(nil);
 
  FirstProcess.Options  := [poUsePipes];
  SecondProcess.Options := [poUsePipes,poStderrToOutPut];
  
  FirstProcess.CommandLine  := 'pwd';
  SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -';    
  // これで "pwd | grep / -" と同等になるはずです
  
  FirstProcess.Execute;
  SecondProcess.Execute;
  
  while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
  begin
    if FirstProcess.Output.NumBytesAvailable > 0 then
    begin
      // 読み込むデータ量がバッファの大きさを超えないようにする
      ReadSize := FirstProcess.Output.NumBytesAvailable;
      if ReadSize > SizeOf(Buffer) then
        ReadSize := SizeOf(Buffer);
      // 出力をバッファに読み込む
      ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
      // バッファの中身を第二のプロセスに書き込む
      SecondProcess.Input.Write(Buffer[0], ReadCount);
  
      // 第二のプロセスが出力するデータが多過ぎるとデッドロックを起こします。
      // その前にデータを読まなければなりません。
      // 「巨大な出力を読み取る」の例をご覧下さい。
    end;
  end;
  // 第二のプロセスの入力を閉じます。
  // これで第二のプロセスの処理が終わります。
  SecondProcess.CloseInput;
 
  // 終了を待ちます。
  // どんなコマンドを実行するか良く注意すべきです。
  // 入力を閉じても終了しないコマンドだと、
  // 次の行が無限ループに突入します。
  while SecondProcess.Running do
    Sleep(1);
  // こんな感じ。ここから先も少し役立ちますよ。

  // バッファを第二プロセスの出力用に再利用します。
  // 出力はこのプログラムの標準出力に送られます。
  WriteLn('Grep output Start:');
  ReadSize := SecondProcess.Output.NumBytesAvailable;
  if ReadSize > SizeOf(Buffer) then
    ReadSize := SizeOf(Buffer);
  if ReadSize > 0 then
  begin
    ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);
    WriteLn(Copy(Buffer,0, ReadCount));
  end
  else
    WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);
  WriteLn('Grep output Finish:');
  
  // プロセスオブジェクトを解放します
  FirstProcess.Free;
  SecondProcess.Free;
end.

はい、これであるプログラムの出力を別のプログラムやファイルに流し込むことができました。

この例は、「やりすぎ」に見えるかもしれません。というのも、TProcess からシェルを起動してやれば「複雑な」コマンドも実行できるからです。こんなふうに:

Process.Commandline := 'sh -c "pwd | grep / -"';

しかし、上記の例はもっとクロスプラットフォームです。Windows でも Linux でもそのまま走ります。"sh" コマンドがあろうがなかろうが構いません(UNIX系のシステムにしかないのが普通です)。また、この例はもっと柔軟です。各プロセス独立にそれぞれの標準入力、標準出力、標準エラー出力を読み書きすることができるからです。プロジェクトの中で、これが利点となることがあるでしょう。

入出力のリダイレクトと root 権限での実行

UNIXやOS Xでいつも問題になるのが、プログラムを root 権限で(あるいはもっと一般的に、他のユーザアカウントで)実行したい場合です。ping コマンドを走らせるのが実例になるでしょう。

If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([1]). This sample runs ls on the /root directory, but can of course be adapted.

A 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.

Large 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.

program rootls;

{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,
calling sudo on Linux/macOS, and supplying input on stdin}
{$mode objfpc}{$H+}

uses
  Classes,
  Math, {for min}
  Process;

  procedure RunsLsRoot;
  var
    Proc: TProcess;
    CharBuffer: array [0..511] of char;
    ReadCount: integer;
    ExitCode: integer;
    SudoPassword: string;
  begin
    WriteLn('Please enter the sudo password:');
    Readln(SudoPassword);
    ExitCode := -1; //Start out with failure, let's see later if it works
    Proc := TProcess.Create(nil); //Create a new process
    try
      Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr
      Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo
      // -S causes sudo to read the password from stdin.
      Proc.Execute; //start it. sudo will now probably ask for a password

      // write the password to stdin of the sudo program:
      SudoPassword := SudoPassword + LineEnding;
      Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
      SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof
      SudoPassword := ''; // and make the program a bit safer from snooping?!?

      // main loop to read output from stdout and stderr of sudo
      while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
        (Proc.Stderr.NumBytesAvailable > 0) do
      begin
        // read stdout and write to our stdout
        while Proc.Output.NumBytesAvailable > 0 do
        begin
          ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more
          Proc.Output.Read(CharBuffer, ReadCount);
          Write(StdOut, Copy(CharBuffer, 0, ReadCount));
        end;
        // read stderr and write to our stderr
        while Proc.Stderr.NumBytesAvailable > 0 do
        begin
          ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more
          Proc.Stderr.Read(CharBuffer, ReadCount);
          Write(StdErr, Copy(CharBuffer, 0, ReadCount));
        end;
      end;
      ExitCode := Proc.ExitStatus;
    finally
      Proc.Free;
      Halt(ExitCode);
    end;
  end;

begin
  RunsLsRoot;
end.

Other 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.

Using fdisk with sudo on Linux

The following example shows how to run fdisk on a Linux machine using the sudo command to get root permissions.

program getpartitioninfo;
{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc.
Modified from Lazarus snippet to FPC program for ease of understanding/conciseness by BigChimp}

Uses
  Classes, SysUtils, FileUtil, Process;

var
  hprocess: TProcess;
  sPass: String;
  OutputLines: TStringList;

begin  
  sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password
  OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure 
  // OutputLines is freed... Same for hProcess.
     
  // The following example will open fdisk in the background and give us partition information
  // Since fdisk requires elevated priviledges we need to 
  // pass our password as a parameter to sudo using the -S
  // option, so it will will wait till our program sends our password to the sudo application
  hProcess := TProcess.Create(nil);
  // On Linux/Unix/macOS, we need specify full path to our executable:
  hProcess.Executable := '/bin/sh';
  // Now we add all the parameters on the command line:
  hprocess.Parameters.Add('-c');
  // Here we pipe the password to the sudo command which then executes fdisk -l: 
  hprocess.Parameters.add('echo ' + sPass  + ' | sudo -S fdisk -l');
  // Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe
  hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
  // Now run:
  hProcess.Execute;

  // hProcess should have now run the external executable (because we use poWaitOnExit).
  // Now you can process the process output (standard output and standard error), eg:
  OutputLines.Add('stdout:');
  OutputLines.LoadFromStream(hprocess.Output);
  OutputLines.Add('stderr:');
  OutputLines.LoadFromStream(hProcess.Stderr);
  // Show output on screen:
  writeln(OutputLines.Text);

  // Clean up to avoid memory leaks:
  hProcess.Free;
  OutputLines.Free;
  
  //Below are some examples as you see we can pass illegal characters just as if done from terminal 
  //Even though you have read elsewhere that you can not I assure with this method you can :)

  //hprocess.Parameters.Add('ping -c 1 www.google.com');
  //hprocess.Parameters.Add('ifconfig wlan0 | grep ' +  QuotedStr('inet addr:') + ' | cut -d: -f2');

  //Using QuotedStr() is not a requirement though it makes for cleaner code;
  //you can use double quote and have the same effect.

  //hprocess.Parameters.Add('glxinfo | grep direct');   

  // This method can also be used for installing applications from your repository:

  //hprocess.Parameters.add('echo ' + sPass  + ' | sudo -S apt-get install -y pkg-name'); 

 end.


空白を含む引数 (シェルの引用符を置き換える)

Linuxのシェルでは、引数を引用符でくるむことができます。たとえばこのように:

gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram

このとき、GDB が受け取る引数の数は 3 です(その他に、第0の引数として、実行ファイルのフルパスが渡されます):

  1. --batch
  2. --eval-command=info symbol 0x0000DDDD
  3. the full path to myprogram

TProcess は空白を含む引数を渡せますが、引数の一部を囲むのではなく、全体を囲みます。このように:

TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';

フルパスしか渡せないことを思い出してください。

これに関連して、次の議論をお読みください: http://bugs.freepascal.org/view.php?id=14446

(Process.)RunCommand

FPC 2.6.2 では、Process ユニットに、TProcess のヘルパ関数が加えられました。これは fpcup プロジェクトで用いられた TProcess のラッパをもとにしています。 これらの関数は、基礎から中級クラスの用法を想定しており、一行から、大量の出力行までサポートしています。

以下、簡単な用例です

uses Process;
...
var s : ansistring;
...
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
   writeln(s);

オーヴァロードした新たな RunCommand はプログラムの終了コードを返します。RunCommandInDir は、コマンドを p.CurrentDirectory で決めた他のディレクトリで実行します:

function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;

LCLIntf による別法

特定の外部プログラムを明示的に呼ぶ必要がなく、ある文書を開くためになんらかのアプリケーションが動けばいいという場合があります。どのアプリケーションでその文書を開くかは OS まかせで、文書のタイプに合わせたデフォルトのアプリケーションが用いられます。次にいくつか例を挙げます。

デフォルトのアプリケーションで文書を開く

特定のプログラムを実行したいというより、ある文書/ファイルを、関連づけられたデフォルトのアプリケーションで開きたい場合があります。 これは OS に依存します。Lazarus ではこのような場合のためにプラットフォームに依存しない手続き、OpenDocument を用意しています。呼出し側はブロックされずそのまま実行を続けます。

uses LCLIntf;
...
OpenDocument('manual.pdf');  
...

デフォルトのWebブラウザでWebページを開く

OpenURL に開きたい URL を渡すだけです。場合によっては、最初の http:// は不要です。ファイル名を渡すと OpenDocument() と同じ結果が得られます。

uses LCLIntf;
...
OpenURL('www.lazarus.freepascal.org/');

関連項目:

TProcess をこんなふうに使ってもいけます:

uses Process;

procedure OpenWebPage(URL: string);
// URL は "" でかこってください。例えば、"www.lazarus.freepascal.org"
var
  Browser, Params: string;
begin
  FindDefaultBrowser(Browser, Params);
  with TProcess.Create(nil) do
  try
    Executable := Browser;
    Params:=Format(Params, [URL]);
    Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself
    Parameters.Add(Params);
    Options := [poNoConsole];
    Execute;
  finally
    Free;
  end;
end;

関連項目