Difference between revisions of "Executing External Programs/zh CN"

From Lazarus wiki
Jump to navigationJump to search
(Blanked the page)
m (Bypass openurl redirection to OpenUrl)
 
(29 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
{{Executing External Programs}}
  
 +
== 概述:对比 ==
 +
在RTL(运行时库)中有不同的可用的方法,FCL和LCL库,关于如何执行一个外部command(命 令)/process(进程)/program(程序).
 +
{| class="wikitable"
 +
!Method
 +
!Library
 +
!Platforms
 +
!Single Line
 +
!Features
 +
|-
 +
|[[Executing External Programs#SysUtils.ExecuteProcess|ExecuteProcess]]
 +
|RTL
 +
|跨平台
 +
|是
 +
|非常有限,同时存在
 +
|-
 +
|[[Executing External Programs#MS Windows : CreateProcess, ShellExecute and WinExec|ShellExecute]]
 +
|WinAPI
 +
|仅微软Windows系统
 +
|是
 +
|很多。可以开始带有elevation/admin权限的程序。
 +
|-
 +
|[[Executing External Programs#Unix fpsystem, fpexecve and shell |fpsystem, fpexecve]]
 +
|Unix
 +
|仅Unix系统
 +
|
 +
|
 +
|-
 +
|[[Executing External Programs#TProcess|TProcess]]
 +
|FCL
 +
|跨平台
 +
| 否
 +
|完全
 +
|-
 +
|[[Executing External Programs#(Process.)RunCommand|RunCommand]]
 +
|FCL
 +
|跨平台 需要FPC 2.6.2+
 +
|是
 +
|涉及共有的TProcess用法
 +
|-
 +
|[[Executing External Programs#LCLIntf Alternatives|OpenDocument]]
 +
|LCL
 +
|跨平台
 +
|是
 +
|仅打开文档。文档可以使用相关联该文档类型的应用程序打开。
 +
|}
 +
 +
==(Process.)RunCommand==
 +
* [http://www.freepascal.org/docs-html/fcl/process/runcommand.html RunCommand] 引 用
 +
* [http://www.freepascal.org/docs-html/fcl/process/runcommandindir.html RunCommandInDir] 引 用
 +
 +
在FPC 2.6.2中,对TProcess的一些有帮助的函数被添加到单元process,基于使用在[[Projects using Lazarus#fpcup|fpcup]]项目的封装 (wrapper)。
 +
这些函数为基础和中间物做准备,并且可以捕获输出到一个唯一的字符串,并完全支持大量输出的情况
 +
 +
一个简单的示例是
 +
<syntaxhighlight lang=pascal>
 +
uses Process;
 +
...
 +
var s : ansistring;
 +
...
 +
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
 +
  writeln(s);
 +
</syntaxhighlight>
 +
RunCommand的一个超载(overloaded)变体,返回程序的退出代码。RunCommandInDir 在 一个不同的目录中运行命令(设置 p.CurrentDirectory):
 +
<syntaxhighlight lang=pascal>
 +
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;
 +
</syntaxhighlight>
 +
 +
== SysUtils.ExecuteProcess ==
 +
(跨平台)<BR>
 +
虽有一个数的限制,最简单的方法来启动一个程序(模态,无管道或一些控件的形式)是简单地使用 :
 +
 +
<syntaxhighlight lang=pascal>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight>
 +
 +
调用进程同步运行:它'挂起(hangs)',直到内部出现完成 - 但是,在你的程序中继续前,如果你想要用户来做 一些事,这可能是有用的。对于更多用途的方法,看下一关于优先考虑跨平台的'''TProcess'''部分, 或如果你仅希望瞄准Windows,你可以使用'''ShellExecute'''.
 +
 +
* [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess引用]
 +
 +
== 微软Windows系统  : CreateProcess, ShellExecute 和 WinExec ==
 +
 +
{{Note|尽管FPC/Lazarus已经支持'''CreateProcess''', '''ShellExecute''' 和/或 '''WinExec''',这个支持仅在in32/64中。如果你的程序是跨平台的,考虑使用'''RunCommand''' or '''TProcess'''.}}
 +
{{Note|在Windows API中 WinExec是一个很多年被不赞成的16-位调用。在当前的FPC版本中,它生成一个警告.}}
 +
 +
'''ShellExecute''' 是一个标准的微软Windows函数(ShellApi.h),在MSDN上有很好的文档(如果你发现函数不可靠,注意它们关于初始化COM的话语).
 +
 +
<syntaxhighlight lang=pascal>
 +
uses ..., ShellApi;
 +
 +
// Simple one-liner (ignoring error returns) :
 +
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;
 +
 +
// Execute a Batch File :
 +
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;
 +
 +
// Open a command window in a given folder :
 +
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;
 +
 +
// Open a webpage URL in the default browser using 'start' command (via a brief hidden cmd window) :
 +
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;
 +
 +
// or a useful procedure:
 +
procedure RunShellExecute(const prog,params:string);
 +
begin
 +
  //  ( Handle, nil/'open'/'edit'/'find'/'explore'/'print',  // 'open' isn't always needed
 +
  //      path+prog, params, working folder,
 +
  //        0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min)  // for SW_ constants : uses ... Windows ...
 +
  if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //success
 +
  // return values 0..32 are errors
 +
end;
 +
</syntaxhighlight>
 +
 +
那儿也有ShellExecuteExW作为一个WideChar版本,并且ShellExecuteExA是 AnsiChar.
 +
 +
fMask选项也能使用SEE_MASK_DOENVSUBST或SEE_MASK_FLAG_NO_UI或 SEE_MASK_NOCLOSEPROCESS,等.
 +
 +
如果在Delphi中,你对'''文档'''使用ShellExecute,像Word文档,或URLs,看看 open* (OpenUrl等等) 函数,在lclintf中 (看这页下面的可选择部分).
 +
 +
=== 使用ShellExecuteEx对于elevation/administrator权限 ===
 +
如果你需要执行带有administrator/elevated权限的外部程序,你可以使用'''runas'''方 法替换ShellExecuteEx函数:
 +
<syntaxhighlight lang=pascal>
 +
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;
 +
</syntaxhighlight>
 +
 +
== Unix系统 fpsystem, fpexecve和shell ==
 +
 +
这些函数依赖平台.
 +
 +
* [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem 引用]
 +
* [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve 引用]
 +
* [http://www.freepascal.org/docs-html/rtl/unix/shell.html shell 引用]
 +
 +
注意,1.0.x 'Unix.Shell 已经不赞成一段时间了,在trunk(主支)中被移除。使用psystem. .
 +
 +
== TProcess ==
 +
 +
你可以使用TProcess来启动外部程序。使用TProcess的一些好处是:
 +
 +
* 平台独立
 +
* 从stdout读和写到stdin的能力 .
 +
* 能做到等待一个命令完成或当你的程序移动时让它运行.
 +
 +
重要注释:
 +
* TTProcess不是一个terminal(控制台)/shell!你不能直接执行scripts(脚本),或使用运算符像"|", ">","<", "&"等等重定向输出,使用pascal的TProcess获得相同的结果 是可能的,一些示例在下面..
 +
* 很可能在Linux/Unix上:你'''must'''指定完整的路径到可执行文件。例如:'/bin/cp' 代替'cp'。如果程序是在标准的PATH(路径)上,那么你可以从LCL的单元[[doc:lcl/fileutil/index.html|FileUtil]],使用函数[[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]].
 +
* 在Windows上,如果该命令是在路径中,你不需要指定完整的路径.
 +
* [[doc:fcl/process/tprocess.html|TProcess 引用]]
 +
 +
=== 最简单的示例 ===
 +
 +
很多的典型情况被事先准备在 [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]函数中。在你开始复制和粘贴下面的示例前,首先核查它们.
 +
 +
=== 一个简单的示例 ===
 +
这个示例('''这不能使用在生产中,看大量输出,或更好的, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''')仅向你显示如何运行一个外部程序:
 +
<syntaxhighlight lang=pascal>
 +
// This is a demo program that shows
 +
// how to launch an external program.
 +
program launchprogram;
 +
 +
// Here we include files that have useful functions
 +
// and procedures we will need.
 +
uses
 +
  Classes, SysUtils, Process;
 +
 +
// This defines the var "AProcess" as a variable
 +
// of the type "TProcess"
 +
var
 +
  AProcess: TProcess;
 +
 +
// This is where our program starts to run
 +
begin
 +
  // Now we will create the TProcess object, and
 +
  // assign it to the var AProcess.
 +
  AProcess := TProcess.Create(nil);
 +
 +
  // Tell the new AProcess what the command to execute is.
 +
  // Let's use the Free Pascal compiler (i386 version that is)
 +
  AProcess.Executable:= 'ppc386';
 +
 +
  // Pass -h together with ppc386 so actually 'ppc386 -h' is executed:
 +
  AProcess.Parameters.Add('-h');
 +
 +
  // We will define an option for when the program
 +
  // is run. This option will make sure that our program
 +
  // does not continue until the program we will launch
 +
  // has stopped running.                vvvvvvvvvvvvvv
 +
  AProcess.Options := AProcess.Options + [poWaitOnExit];
 +
 +
  // Now let AProcess run the program
 +
  AProcess.Execute;
 +
 +
  // This is not reached until ppc386 stops running.
 +
  AProcess.Free; 
 +
end.</syntaxhighlight>
 +
就是这样,你刚刚学习从你的程序内部来运行一个外部程序.
 +
 +
=== 一个提 高的示例(但是现在不正确)===
 +
这是好的,但是,我如何读一个我运行程序的输出?
 +
 +
好的,让我们稍微扩张我们的示例,并马上就要做这:
 +
'''这个示例保持简单,因此你可以从它学习。请不要在产品代码中 使用这个示例,但是使用代码在 [[#读大量输出]].'''
 +
 +
<syntaxhighlight lang=pascal>
 +
// This is a
 +
// FLAWED
 +
// demo program that shows
 +
// how to launch an external program
 +
// and read from its output.
 +
program launchprogram;
 +
 +
// Here we include files that have useful functions
 +
// and procedures we will need.
 +
uses
 +
  Classes, SysUtils, Process;
 +
 +
// This is defining the var "AProcess" as a variable
 +
// of the type "TProcess"
 +
// Also now we are adding a TStringList to store the
 +
// data read from the programs output.
 +
var
 +
  AProcess: TProcess;
 +
  AStringList: TStringList;
 +
 +
// This is where our program starts to run
 +
begin
 +
  // Now we will create the TProcess object, and
 +
  // assign it to the var AProcess.
 +
  AProcess := TProcess.Create(nil);
 +
 +
  // Tell the new AProcess what the command to execute is.
 +
  AProcess.Executable := '/usr/bin/ppc386';
 +
  AProcess.Parameters.Add('-h');
 +
 +
  // We will define an option for when the program
 +
  // is run. This option will make sure that our program
 +
  // does not continue until the program we will launch
 +
  // has stopped running. Also now we will tell it that
 +
  // we want to read the output of the file.
 +
  AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 +
 +
  // Now that AProcess knows what the commandline is it can be run.
 +
  AProcess.Execute;
 +
 
 +
  // After AProcess has finished, the rest of the program will be executed.
 +
 +
  // Now read the output of the program we just ran into a TStringList.
 +
  AStringList := TStringList.Create;
 +
  AStringList.LoadFromStream(AProcess.Output);
 +
 
 +
  // Save the output to a file and clean up the TStringList.
 +
  AStringList.SaveToFile('output.txt');
 +
  AStringList.Free;
 +
 +
  // Now that the output from the process is processed, it can be freed.
 +
  AProcess.Free; 
 +
end.</syntaxhighlight>
 +
 +
=== 读大量输出 ===
 +
在前一个示例中,我们一直等到程序退出。然后,我们读什么程序写到它的输出。
 +
 +
假设程序写很多数据到输出。然后,输出管道变满,并且调用程序等待,直到管道被读取。
 +
 +
但是,调用程序不从它读,直到调用的程序被结束。一个停滞发生。
 +
 +
下面的示例因为不使用poWaitOnExit,但是,当出现仍然在运行时,从输出读取。输出被存储在内存流中,随后 可以被使用于读输出到一个TStringList中。
 +
 +
如果你想从一个外部进程中读输出,这是你需要改写到产品使用的代码.
 +
<syntaxhighlight lang=pascal>program LargeOutputDemo;
 +
 +
{$mode objfpc}{$H+}
 +
 +
uses
 +
  Classes, SysUtils, Process; // Process is the unit that holds TProcess
 +
 +
const
 +
  BUF_SIZE = 2048; // Buffer size for reading the output in chunks
 +
 +
var
 +
  AProcess    : TProcess;
 +
  OutputStream : TStream;
 +
  BytesRead    : longint;
 +
  Buffer      : array[1..BUF_SIZE] of byte;
 +
 +
begin
 +
  // Set up the process; as an example a recursive directory search is used
 +
  // because that will usually result in a lot of data.
 +
  AProcess := TProcess.Create(nil);
 +
 +
  // The commands for Windows and *nix are different hence the $IFDEFs
 +
  {$IFDEF Windows}
 +
    // In Windows the dir command cannot be used directly because it's a build-in
 +
    // shell command. Therefore cmd.exe and the extra parameters are needed.
 +
    AProcess.Executable := 'c:\windows\system32\cmd.exe';
 +
    AProcess.Parameters.Add('/c');
 +
    AProcess.Parameters.Add('dir /s c:\windows');
 +
  {$ENDIF Windows}
 +
 +
  {$IFDEF Unix}
 +
    AProcess.Executable := '/bin/ls';
 +
    AProcess.Parameters.Add('--recursive');
 +
    AProcess.Parameters.Add('--all');
 +
    AProcess.Parameters.Add('-l');
 +
  {$ENDIF Unix}
 +
 +
  // Process option poUsePipes has to be used so the output can be captured.
 +
  // Process option poWaitOnExit can not be used because that would block
 +
  // this program, preventing it from reading the output data of the process.
 +
  AProcess.Options := [poUsePipes];
 +
 +
  // Start the process (run the dir/ls command)
 +
  AProcess.Execute;
 +
 +
  // Create a stream object to store the generated output in. This could
 +
  // also be a file stream to directly save the output to disk.
 +
  OutputStream := TMemoryStream.Create;
 +
 +
  // All generated output from AProcess is read in a loop until no more data is available
 +
  repeat
 +
    // Get the new data from the process to a maximum of the buffer size that was allocated.
 +
    // Note that all read(...) calls will block except for the last one, which returns 0 (zero).
 +
    BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
 +
 +
    // Add the bytes that were read to the stream for later usage
 +
    OutputStream.Write(Buffer, BytesRead)
 +
 +
  until BytesRead = 0;  // Stop if no more data is available
 +
 +
  // The process has finished so it can be cleaned up
 +
  AProcess.Free;
 +
 +
  // Now that all data has been read it can be used; for example to save it to a file on disk
 +
  with TFileStream.Create('output.txt', fmCreate) do
 +
  begin
 +
    OutputStream.Position := 0; // Required to make sure all data is copied from the start
 +
    CopyFrom(OutputStream, OutputStream.Size);
 +
    Free
 +
  end;
 +
 +
  // Or the data can be shown on screen
 +
  with TStringList.Create do
 +
  begin
 +
    OutputStream.Position := 0; // Required to make sure all data is copied from the start
 +
    LoadFromStream(OutputStream);
 +
    writeln(Text);
 +
    writeln('--- Number of lines = ', Count, '----');
 +
    Free
 +
  end;
 +
 +
  // Clean up
 +
  OutputStream.Free;
 +
end.</syntaxhighlight>
 +
注意,上面应使用RunCommand完成:
 +
<syntaxhighlight lang=pascal>var s: string;
 +
...
 +
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight>
 +
 +
=== 使用一个TProcess的 输入和输出 ===
 +
查看processdemo示例,在[https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process Lazarus-CCR SVN].
 +
 +
=== 关于TProcess的使用 的提示 ===
 +
当创建一个跨平台程序,指定的操作系统可执行文件名称可以使用指令"{$IFDEF}"和{$ENDIF}"设置.
 +
 +
示例:
 +
<syntaxhighlight lang=pascal>{...}
 +
AProcess := TProcess.Create(nil)
 +
 +
{$IFDEF WIN32}
 +
  AProcess.Executable := 'calc.exe';
 +
{$ENDIF}
 +
 +
{$IFDEF LINUX}
 +
  AProcess.Executable := FindDefaultExecutablePath('kcalc');
 +
{$ENDIF}
 +
 +
AProcess.Execute;
 +
{...}</syntaxhighlight>
 +
 +
=== OS X 在前台显示应用程序包 ===
 +
 +
你可以凭借TProcess通过在软件包中启动可执行文件开始一个'''应用程序包'''. 例如:
 +
<syntaxhighlight lang=pascal>
 +
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';
 +
</syntaxhighlight>
 +
 +
这将开始''Calendar'',但是窗口讲在当前应用程序的后面。为在前台获取应用程序,你可以使用 '''open'''能,带有'''-n'''参数:
 +
<syntaxhighlight lang=pascal>
 +
AProcess.Executable:='/usr/bin/open';
 +
AProcess.Parameters.Add('-n');
 +
AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole
 +
AProcess.Parameters.Add('/Application/iCal.app');
 +
</syntaxhighlight>
 +
 +
如果你的应用程序需要参数,你可以传送'''open'''程序的'''--args'''参数,在所有的参赛被传送到应用程序后:
 +
<syntaxhighlight lang=pascal>
 +
AProcess.Parameters.Add('--args');
 +
AProcess.Parameters.Add('argument1');
 +
AProcess.Parameters.Add('argument2');
 +
</syntaxhighlight>
 +
 +
=== 运行独立的程序 ===
 +
 +
一般的,由你的应用程序开始的程序是一个子进程,当你的应用程序被杀死时,亦被杀死。.当你想运行 一个独立的保持运行的程序,你可以使用下面:
 +
 +
<syntaxhighlight lang=pascal>
 +
var
 +
  Process: TProcess;
 +
  I: Integer;
 +
begin
 +
  Process := TProcess.Create(nil);
 +
  try
 +
    Process.InheritHandles := False;
 +
    Process.Options := [];
 +
    Process.ShowWindow := swoShow;
 +
 +
    // Copy default environment variables including DISPLAY variable for GUI application to work
 +
    for I := 1 to GetEnvironmentVariableCount do
 +
      Process.Environment.Add(GetEnvironmentString(I));
 +
 +
    Process.Executable := '/usr/bin/gedit'; 
 +
    Process.Execute;
 +
  finally
 +
    Process.Free;
 +
  end;
 +
end;
 +
</syntaxhighlight>
 +
 +
=== "讨论" 带有aspell进程的示例 ===
 +
 +
在[https://github.com/pasdoc/pasdoc/wiki pasdoc]源 文件代码里面,你可以找到两个单元,这通过管道(pipes)经由"讨论"带有aspell进程的执行(perform) 拼写检查:
 +
 +
* [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit]实施TProcessLineTalk类,TProcess的衍生物,这可以被简单地用于讨论一些关于逐行基础(basis) 的进程.
 +
 +
* [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units]实施TAspellProcess类,这履行拼写检查,通过使用下层的TProcessLineTalk示例老执行aspell和与运行的aspell进 程取得联系.
 +
 +
这两个单元都与pasdoc源文件的剩余部分相当独立,因此,它们可以充当使用TProcess来运行并通过管道与其 它程序通讯的真实的示例.
 +
 +
=== 替换shell运算符,像 "| < >" ===
 +
 +
有时,你想运行更复杂的命令,命令传输它的数据到另一个命令或文件。一些东西看起来
 +
<syntaxhighlight lang=pascal>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight>
 +
 +
<syntaxhighlight lang=pascal>ShellExecute('dir > output.txt');</syntaxhighlight>
 +
 +
使用TProcess执行这个将被不工作,例如:
 +
 +
<syntaxhighlight lang=pascal>// this won't work
 +
Process.CommandLine := 'firstcommand.exe | secondcommand.exe';
 +
Process.Execute;</syntaxhighlight>
 +
 +
==== 为什么使用指定运算符到重定向输出不工作 ====
 +
TProcess仅仅是这,它不是一个shell环境,仅是一个进程process(进程)。它不是两个进程,它仅是 一个。无论怎样你想要方法的重定向输出是运行的。看[[Executing_External_Programs#How_to_redirect_output_with_TProcess |下一部分]].
 +
 +
=== 如何使用TProcess 重定向输出 ===
 +
 +
你可以为'''每一个'''命令使用一个TProcess实例重定向一个命令的输出到另一个命令.
 +
 +
这是一个示例,解释重定向一个进程的输出到另一个。为重定向一个进程的输出到一个文件/流,看示例 [[Executing_External_Programs#Reading_large_output | 读大量输出]]
 +
 +
不仅你可以重定向"正常"输出(也被称为stdout),但是你也可以重定向错误输出(stderr),如果你指定 poStderrToOutPut选项,像在第二个进程选项中所看.
 +
 +
<syntaxhighlight lang=pascal>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];
 +
  FirstProcess.Executable  := 'pwd';
 +
 
 +
  SecondProcess.Options    := [poUsePipes,poStderrToOutPut];
 +
  SecondProcess.Executable := 'grep';
 +
  SecondProcess.Parameters.Add(DirectorySeparator+ ' -');
 +
  // this would be the same as "pwd | grep / -"
 +
 
 +
  FirstProcess.Execute;
 +
  SecondProcess.Execute;
 +
 
 +
  while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
 +
  begin
 +
    if FirstProcess.Output.NumBytesAvailable > 0 then
 +
    begin
 +
      // make sure that we don't read more data than we have allocated
 +
      // in the buffer
 +
      ReadSize := FirstProcess.Output.NumBytesAvailable;
 +
      if ReadSize > SizeOf(Buffer) then
 +
        ReadSize := SizeOf(Buffer);
 +
      // now read the output into the buffer
 +
      ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
 +
      // and write the buffer to the second process
 +
      SecondProcess.Input.Write(Buffer[0], ReadCount);
 +
 
 +
      // if SecondProcess writes much data to it's Output then
 +
      // we should read that data here to prevent a deadlock
 +
      // see the previous example "Reading Large Output"
 +
    end;
 +
  end;
 +
  // Close the input on the SecondProcess
 +
  // so it finishes processing it's data
 +
  SecondProcess.CloseInput;
 +
 +
  // and wait for it to complete
 +
  // be carefull what command you run because it may not exit when
 +
  // it's input is closed and the following line may loop forever
 +
  while SecondProcess.Running do
 +
    Sleep(1);
 +
  // that's it! the rest of the program is just so the example
 +
  // is a little 'useful'
 +
 +
  // we will reuse Buffer to output the SecondProcess's
 +
  // output to *this* programs stdout
 +
  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:');
 +
 
 +
  // free our process objects
 +
  FirstProcess.Free;
 +
  SecondProcess.Free;
 +
end.</syntaxhighlight>
 +
 +
这是全部。现在你可以从一个程序重定向输出到另一个.
 +
 +
==== 注意 ====
 +
这个示例可能看着太夸张,尽管它能使用一个shell带有TProcess来运行"结构复杂的"命令,像:
 +
<syntaxhighlight lang=pascal>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight>
 +
 +
但是我们的示例是更跨平台的,尽管它在Windows或Linuc等等上不需要修改. "sh"可能或不可能存在在你的 平台上,一般是仅在*nix平台上。另外,在我们的示例中,我们有更多的灵活性,尽管你可以各自地读和写从/到输入,输 出和每个标准错误(stderr),这对你的工程可能非常有利.
 +
 +
===在 root下,重定向输入和输出,并运行===
 +
在Unixes (OSX)和Linux上的一个一般问题是,你想在root账户(或,更一般地,其他的用户账户)下执行一些程序。一个示例将运行 ''ping'' 命令.
 +
 +
如果你可以对这使用sudo。你可以改写(adapt)下面改写自andman张贴在论坛 ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]) 上的示例. 这个示例在  <code>/root</code> 目录运行<code>ls</code>, 但是,当然可以被改写.
 +
 +
一个做这个'''更好的方法'''是使用policykit软件包,这应该在所有最近的Linux上是可用的. [http://lazarus.freepascal.org/index.php/topic,14479.0.html 详细信息查看论坛系列相关信息.]
 +
 +
这个代码的大部分类似于早期的示例,但是它也显示如何单独地调用重定向进程的(redirect)标准输出和标准错误 (stderr)到我们拥有代码的标准输出和标准错误(stderr).
 +
 +
<syntaxhighlight lang=pascal>
 +
program rootls;
 +
 +
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,
 +
calling sudo on Linux/OSX, 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.
 +
</syntaxhighlight>
 +
 +
其他想法:
 +
它可能固然是明智的 来看看是否sudo确实提示输入密码。这可以始终如一的核查,通过设置环境变量SUDO_PROMPT到 我们观察读TProcess的标准输出(stdout)避免对不同地点的提示问题不同时候的一些事.设置一个环境变量导 致默认值被清理(继承自我们的进程(process)),所以,如果需要我们不得不从我们的程序中复制环境.
 +
 +
=== 在Linux上带有sudo使用 fdisk ===
 +
下面的示例显示在一个Linux机器上如何使用sudo命令获取root权限来运行fdisk. '''注意:这仅是一 个示例,并不适合大量输出.'''
 +
 +
<syntaxhighlight lang=pascal>
 +
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/OSX, 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.     
 +
</syntaxhighlight>
 +
 +
===参数含有空格(替换Shell 引用)===
 +
 +
在Linux shell中,写引用参数是可接受的,像这:
 +
 +
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram
 +
 +
并且GDB将接受3个参数(除第一个参数外,它是完整的路径到可执行文件):
 +
#--batch
 +
#--eval-command=info symbol 0x0000DDDD
 +
#完整的路径到myprogram
 +
 +
TProcess也可以传递保护空格的参数,但是它使用一个不同的引用样式。而不是仅引用参数的部分,引用参数的全 部。像这样:
 +
 +
TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';
 +
 +
并且也记住仅传递完整的路径.
 +
 +
参考关于它的这个讨论: http://bugs.freepascal.org/view.php?id=14446
 +
 +
==LCLIntf替代方案==
 +
有时,你不需要明确地调用一个外部程序来获取你需要的功能。作为打开一个应用程序和指定的文 件来跟它相配的代替,仅要求OS来打开文档和让它使用与这些文件类型相关联的默认应用程序、下面是一些示例.
 +
 +
===打开文档在默认应用程序中===
 +
 +
在一些情况下,你需要使用默认相关联的应用程序打开一下文档/文件,而不是执行一个独有的程序。这依赖于运行的操作系统。Lazarus通过一个平台独立procedure(过程)'''OpenDocument''',这将为你处理它。你的应用程序将持续运行,而不等待文档进程关闭.
 +
 +
<syntaxhighlight lang=pascal>
 +
uses LCLIntf;
 +
...
 +
OpenDocument('manual.pdf'); 
 +
...
 +
</syntaxhighlight>
 +
 +
* [[opendocument|OpenDocument 引用]]
 +
 +
===在默认网页浏览器中打开网页===
 +
 +
J只传递需要的URL,在某些情况下,前列的http://似乎是可选的。并且,传递一个文件名称似乎与给予 OpenDocument()使用LCLIntf的结果相同.
 +
<syntaxhighlight lang=pascal>
 +
uses LCLIntf;
 +
...
 +
OpenURL('www.lazarus.freepascal.org/');
 +
</syntaxhighlight>
 +
 +
请参阅:
 +
* [[OpenUrl|OpenURL reference]]
 +
 +
或,你可以使用 '''TProcess''' 像这:
 +
<syntaxhighlight lang=pascal>
 +
uses Process;
 +
 +
procedure OpenWebPage(URL: string);
 +
// Apparently you need to pass your URL inside ", like "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;
 +
</syntaxhighlight>
 +
 +
==请参阅==
 +
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess 文档]
 +
* [[opendocument|OpenDocument]]
 +
* [[OpenUrl]]
 +
* [[TProcessUTF8]]
 +
* [[TXMLPropStorage]]
 +
* [[Webbrowser |网页浏览器]]
 +
 +
== 贡献者和更改 ==
 +
* 简体中文版本由 robsean 于 2019-03-07 创建。

Latest revision as of 04:09, 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)

概述:对比

在RTL(运行时库)中有不同的可用的方法,FCL和LCL库,关于如何执行一个外部command(命 令)/process(进程)/program(程序).

Method Library Platforms Single Line Features
ExecuteProcess RTL 跨平台 非常有限,同时存在
ShellExecute WinAPI 仅微软Windows系统 很多。可以开始带有elevation/admin权限的程序。
fpsystem, fpexecve Unix 仅Unix系统
TProcess FCL 跨平台 完全
RunCommand FCL 跨平台 需要FPC 2.6.2+ 涉及共有的TProcess用法
OpenDocument LCL 跨平台 仅打开文档。文档可以使用相关联该文档类型的应用程序打开。

(Process.)RunCommand

在FPC 2.6.2中,对TProcess的一些有帮助的函数被添加到单元process,基于使用在fpcup项目的封装 (wrapper)。 这些函数为基础和中间物做准备,并且可以捕获输出到一个唯一的字符串,并完全支持大量输出的情况

一个简单的示例是

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

RunCommand的一个超载(overloaded)变体,返回程序的退出代码。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;

SysUtils.ExecuteProcess

(跨平台)
虽有一个数的限制,最简单的方法来启动一个程序(模态,无管道或一些控件的形式)是简单地使用 :

SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);

调用进程同步运行:它'挂起(hangs)',直到内部出现完成 - 但是,在你的程序中继续前,如果你想要用户来做 一些事,这可能是有用的。对于更多用途的方法,看下一关于优先考虑跨平台的TProcess部分, 或如果你仅希望瞄准Windows,你可以使用ShellExecute.

微软Windows系统 : CreateProcess, ShellExecute 和 WinExec

Light bulb  Note: 尽管FPC/Lazarus已经支持CreateProcess, ShellExecute 和/或 WinExec,这个支持仅在in32/64中。如果你的程序是跨平台的,考虑使用RunCommand or TProcess.
Light bulb  Note: 在Windows API中 WinExec是一个很多年被不赞成的16-位调用。在当前的FPC版本中,它生成一个警告.

ShellExecute 是一个标准的微软Windows函数(ShellApi.h),在MSDN上有很好的文档(如果你发现函数不可靠,注意它们关于初始化COM的话语).

uses ..., ShellApi;

// Simple one-liner (ignoring error returns) :
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;

// Execute a Batch File :
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;

// Open a command window in a given folder :
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;

// Open a webpage URL in the default browser using 'start' command (via a brief hidden cmd window) :
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;

// or a useful procedure:
procedure RunShellExecute(const prog,params:string);
begin
  //  ( Handle, nil/'open'/'edit'/'find'/'explore'/'print',   // 'open' isn't always needed 
  //      path+prog, params, working folder,
  //        0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min)   // for SW_ constants : uses ... Windows ...
  if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //success
  // return values 0..32 are errors
end;

那儿也有ShellExecuteExW作为一个WideChar版本,并且ShellExecuteExA是 AnsiChar.

fMask选项也能使用SEE_MASK_DOENVSUBST或SEE_MASK_FLAG_NO_UI或 SEE_MASK_NOCLOSEPROCESS,等.

如果在Delphi中,你对文档使用ShellExecute,像Word文档,或URLs,看看 open* (OpenUrl等等) 函数,在lclintf中 (看这页下面的可选择部分).

使用ShellExecuteEx对于elevation/administrator权限

如果你需要执行带有administrator/elevated权限的外部程序,你可以使用runas方 法替换ShellExecuteEx函数:

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 已经不赞成一段时间了,在trunk(主支)中被移除。使用psystem. .

TProcess

你可以使用TProcess来启动外部程序。使用TProcess的一些好处是:

  • 平台独立
  • 从stdout读和写到stdin的能力 .
  • 能做到等待一个命令完成或当你的程序移动时让它运行.

重要注释:

  • TTProcess不是一个terminal(控制台)/shell!你不能直接执行scripts(脚本),或使用运算符像"|", ">","<", "&"等等重定向输出,使用pascal的TProcess获得相同的结果 是可能的,一些示例在下面..
  • 很可能在Linux/Unix上:你must指定完整的路径到可执行文件。例如:'/bin/cp' 代替'cp'。如果程序是在标准的PATH(路径)上,那么你可以从LCL的单元FileUtil,使用函数FindDefaultExecutablePath.
  • 在Windows上,如果该命令是在路径中,你不需要指定完整的路径.
  • TProcess 引用

最简单的示例

很多的典型情况被事先准备在 Runcommand函数中。在你开始复制和粘贴下面的示例前,首先核查它们.

一个简单的示例

这个示例(这不能使用在生产中,看大量输出,或更好的, Runcommand)仅向你显示如何运行一个外部程序:

// This is a demo program that shows
// how to launch an external program.
program launchprogram;
 
// Here we include files that have useful functions
// and procedures we will need.
uses 
  Classes, SysUtils, Process;
 
// This defines the var "AProcess" as a variable 
// of the type "TProcess"
var 
  AProcess: TProcess;
 
// This is where our program starts to run
begin
  // Now we will create the TProcess object, and
  // assign it to the var AProcess.
  AProcess := TProcess.Create(nil);
 
  // Tell the new AProcess what the command to execute is.
  // Let's use the Free Pascal compiler (i386 version that is)
  AProcess.Executable:= 'ppc386';

  // Pass -h together with ppc386 so actually 'ppc386 -h' is executed:
  AProcess.Parameters.Add('-h');
 
  // We will define an option for when the program
  // is run. This option will make sure that our program
  // does not continue until the program we will launch
  // has stopped running.                vvvvvvvvvvvvvv
  AProcess.Options := AProcess.Options + [poWaitOnExit];
 
  // Now let AProcess run the program
  AProcess.Execute;
 
  // This is not reached until ppc386 stops running.
  AProcess.Free;   
end.

就是这样,你刚刚学习从你的程序内部来运行一个外部程序.

一个提 高的示例(但是现在不正确)

这是好的,但是,我如何读一个我运行程序的输出?

好的,让我们稍微扩张我们的示例,并马上就要做这: 这个示例保持简单,因此你可以从它学习。请不要在产品代码中 使用这个示例,但是使用代码在 #读大量输出.

// This is a 
// FLAWED
// demo program that shows
// how to launch an external program
// and read from its output.
program launchprogram;
 
// Here we include files that have useful functions
// and procedures we will need.
uses 
  Classes, SysUtils, Process;
 
// This is defining the var "AProcess" as a variable 
// of the type "TProcess"
// Also now we are adding a TStringList to store the 
// data read from the programs output.
var 
  AProcess: TProcess;
  AStringList: TStringList;

// This is where our program starts to run
begin
  // Now we will create the TProcess object, and
  // assign it to the var AProcess.
  AProcess := TProcess.Create(nil);
 
  // Tell the new AProcess what the command to execute is.
  AProcess.Executable := '/usr/bin/ppc386'; 
  AProcess.Parameters.Add('-h'); 

  // We will define an option for when the program
  // is run. This option will make sure that our program
  // does not continue until the program we will launch
  // has stopped running. Also now we will tell it that
  // we want to read the output of the file.
  AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 
  // Now that AProcess knows what the commandline is it can be run.
  AProcess.Execute;
  
  // After AProcess has finished, the rest of the program will be executed.
 
  // Now read the output of the program we just ran into a TStringList.
  AStringList := TStringList.Create;
  AStringList.LoadFromStream(AProcess.Output);
   
  // Save the output to a file and clean up the TStringList.
  AStringList.SaveToFile('output.txt');
  AStringList.Free;
 
  // Now that the output from the process is processed, it can be freed.
  AProcess.Free;   
end.

读大量输出

在前一个示例中,我们一直等到程序退出。然后,我们读什么程序写到它的输出。

假设程序写很多数据到输出。然后,输出管道变满,并且调用程序等待,直到管道被读取。

但是,调用程序不从它读,直到调用的程序被结束。一个停滞发生。

下面的示例因为不使用poWaitOnExit,但是,当出现仍然在运行时,从输出读取。输出被存储在内存流中,随后 可以被使用于读输出到一个TStringList中。

如果你想从一个外部进程中读输出,这是你需要改写到产品使用的代码.

program LargeOutputDemo;

{$mode objfpc}{$H+}

uses
  Classes, SysUtils, Process; // Process is the unit that holds TProcess

const
  BUF_SIZE = 2048; // Buffer size for reading the output in chunks

var
  AProcess     : TProcess;
  OutputStream : TStream;
  BytesRead    : longint;
  Buffer       : array[1..BUF_SIZE] of byte;

begin
  // Set up the process; as an example a recursive directory search is used
  // because that will usually result in a lot of data.
  AProcess := TProcess.Create(nil);

  // The commands for Windows and *nix are different hence the $IFDEFs
  {$IFDEF Windows}
    // In Windows the dir command cannot be used directly because it's a build-in
    // shell command. Therefore cmd.exe and the extra parameters are needed.
    AProcess.Executable := 'c:\windows\system32\cmd.exe';
    AProcess.Parameters.Add('/c');
    AProcess.Parameters.Add('dir /s c:\windows');
  {$ENDIF Windows}

  {$IFDEF Unix}
    AProcess.Executable := '/bin/ls';
    AProcess.Parameters.Add('--recursive');
    AProcess.Parameters.Add('--all');
    AProcess.Parameters.Add('-l');
  {$ENDIF Unix}

  // Process option poUsePipes has to be used so the output can be captured.
  // Process option poWaitOnExit can not be used because that would block
  // this program, preventing it from reading the output data of the process.
  AProcess.Options := [poUsePipes];

  // Start the process (run the dir/ls command)
  AProcess.Execute;

  // Create a stream object to store the generated output in. This could
  // also be a file stream to directly save the output to disk.
  OutputStream := TMemoryStream.Create;

  // All generated output from AProcess is read in a loop until no more data is available
  repeat
    // Get the new data from the process to a maximum of the buffer size that was allocated.
    // Note that all read(...) calls will block except for the last one, which returns 0 (zero).
    BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);

    // Add the bytes that were read to the stream for later usage
    OutputStream.Write(Buffer, BytesRead)

  until BytesRead = 0;  // Stop if no more data is available

  // The process has finished so it can be cleaned up
  AProcess.Free;

  // Now that all data has been read it can be used; for example to save it to a file on disk
  with TFileStream.Create('output.txt', fmCreate) do
  begin
    OutputStream.Position := 0; // Required to make sure all data is copied from the start
    CopyFrom(OutputStream, OutputStream.Size);
    Free
  end;

  // Or the data can be shown on screen
  with TStringList.Create do
  begin
    OutputStream.Position := 0; // Required to make sure all data is copied from the start
    LoadFromStream(OutputStream);
    writeln(Text);
    writeln('--- Number of lines = ', Count, '----');
    Free
  end;

  // Clean up
  OutputStream.Free;
end.

注意,上面应使用RunCommand完成:

var s: string;
...
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);

使用一个TProcess的 输入和输出

查看processdemo示例,在Lazarus-CCR SVN.

关于TProcess的使用 的提示

当创建一个跨平台程序,指定的操作系统可执行文件名称可以使用指令"{$IFDEF}"和{$ENDIF}"设置.

示例:

{...}
AProcess := TProcess.Create(nil)

{$IFDEF WIN32}
  AProcess.Executable := 'calc.exe'; 
{$ENDIF}

{$IFDEF LINUX}
  AProcess.Executable := FindDefaultExecutablePath('kcalc');
{$ENDIF}

AProcess.Execute;
{...}

OS X 在前台显示应用程序包

你可以凭借TProcess通过在软件包中启动可执行文件开始一个应用程序包. 例如:

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

这将开始Calendar,但是窗口讲在当前应用程序的后面。为在前台获取应用程序,你可以使用 open能,带有-n参数:

 AProcess.Executable:='/usr/bin/open';
 AProcess.Parameters.Add('-n');
 AProcess.Parameters.Add('-a'); //optional: to hide terminal - similar to Windows option poNoConsole
 AProcess.Parameters.Add('/Application/iCal.app');

如果你的应用程序需要参数,你可以传送open程序的--args参数,在所有的参赛被传送到应用程序后:

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

运行独立的程序

一般的,由你的应用程序开始的程序是一个子进程,当你的应用程序被杀死时,亦被杀死。.当你想运行 一个独立的保持运行的程序,你可以使用下面:

var
  Process: TProcess;
  I: Integer;
begin
  Process := TProcess.Create(nil);
  try
    Process.InheritHandles := False;
    Process.Options := [];
    Process.ShowWindow := swoShow;

    // Copy default environment variables including DISPLAY variable for GUI application to work
    for I := 1 to GetEnvironmentVariableCount do
      Process.Environment.Add(GetEnvironmentString(I));

    Process.Executable := '/usr/bin/gedit';  
    Process.Execute;
  finally
    Process.Free;
  end;
end;

"讨论" 带有aspell进程的示例

pasdoc源 文件代码里面,你可以找到两个单元,这通过管道(pipes)经由"讨论"带有aspell进程的执行(perform) 拼写检查:

  • PasDoc_ProcessLineTalk.pas unit实施TProcessLineTalk类,TProcess的衍生物,这可以被简单地用于讨论一些关于逐行基础(basis) 的进程.
  • PasDoc_Aspell.pas units实施TAspellProcess类,这履行拼写检查,通过使用下层的TProcessLineTalk示例老执行aspell和与运行的aspell进 程取得联系.

这两个单元都与pasdoc源文件的剩余部分相当独立,因此,它们可以充当使用TProcess来运行并通过管道与其 它程序通讯的真实的示例.

替换shell运算符,像 "| < >"

有时,你想运行更复杂的命令,命令传输它的数据到另一个命令或文件。一些东西看起来

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

ShellExecute('dir > output.txt');

使用TProcess执行这个将被不工作,例如:

// this won't work
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; 
Process.Execute;

为什么使用指定运算符到重定向输出不工作

TProcess仅仅是这,它不是一个shell环境,仅是一个进程process(进程)。它不是两个进程,它仅是 一个。无论怎样你想要方法的重定向输出是运行的。看下一部分.

如何使用TProcess 重定向输出

你可以为每一个命令使用一个TProcess实例重定向一个命令的输出到另一个命令.

这是一个示例,解释重定向一个进程的输出到另一个。为重定向一个进程的输出到一个文件/流,看示例 读大量输出

不仅你可以重定向"正常"输出(也被称为stdout),但是你也可以重定向错误输出(stderr),如果你指定 poStderrToOutPut选项,像在第二个进程选项中所看.

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]; 
  FirstProcess.Executable  := 'pwd'; 
  
  SecondProcess.Options    := [poUsePipes,poStderrToOutPut];
  SecondProcess.Executable := 'grep'; 
  SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); 
  // this would be the same as "pwd | grep / -"
  
  FirstProcess.Execute;
  SecondProcess.Execute;
  
  while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
  begin
    if FirstProcess.Output.NumBytesAvailable > 0 then
    begin
      // make sure that we don't read more data than we have allocated
      // in the buffer
      ReadSize := FirstProcess.Output.NumBytesAvailable;
      if ReadSize > SizeOf(Buffer) then
        ReadSize := SizeOf(Buffer);
      // now read the output into the buffer
      ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
      // and write the buffer to the second process
      SecondProcess.Input.Write(Buffer[0], ReadCount);
  
      // if SecondProcess writes much data to it's Output then 
      // we should read that data here to prevent a deadlock
      // see the previous example "Reading Large Output"
    end;
  end;
  // Close the input on the SecondProcess
  // so it finishes processing it's data
  SecondProcess.CloseInput;
 
  // and wait for it to complete
  // be carefull what command you run because it may not exit when
  // it's input is closed and the following line may loop forever
  while SecondProcess.Running do
    Sleep(1);
  // that's it! the rest of the program is just so the example
  // is a little 'useful'

  // we will reuse Buffer to output the SecondProcess's
  // output to *this* programs stdout
  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:');
  
  // free our process objects
  FirstProcess.Free;
  SecondProcess.Free;
end.

这是全部。现在你可以从一个程序重定向输出到另一个.

注意

这个示例可能看着太夸张,尽管它能使用一个shell带有TProcess来运行"结构复杂的"命令,像:

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

但是我们的示例是更跨平台的,尽管它在Windows或Linuc等等上不需要修改. "sh"可能或不可能存在在你的 平台上,一般是仅在*nix平台上。另外,在我们的示例中,我们有更多的灵活性,尽管你可以各自地读和写从/到输入,输 出和每个标准错误(stderr),这对你的工程可能非常有利.

在 root下,重定向输入和输出,并运行

在Unixes (OSX)和Linux上的一个一般问题是,你想在root账户(或,更一般地,其他的用户账户)下执行一些程序。一个示例将运行 ping 命令.

如果你可以对这使用sudo。你可以改写(adapt)下面改写自andman张贴在论坛 ([1]) 上的示例. 这个示例在 /root 目录运行ls, 但是,当然可以被改写.

一个做这个更好的方法是使用policykit软件包,这应该在所有最近的Linux上是可用的. 详细信息查看论坛系列相关信息.

这个代码的大部分类似于早期的示例,但是它也显示如何单独地调用重定向进程的(redirect)标准输出和标准错误 (stderr)到我们拥有代码的标准输出和标准错误(stderr).

program rootls;

{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,
calling sudo on Linux/OSX, 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.

其他想法: 它可能固然是明智的 来看看是否sudo确实提示输入密码。这可以始终如一的核查,通过设置环境变量SUDO_PROMPT到 我们观察读TProcess的标准输出(stdout)避免对不同地点的提示问题不同时候的一些事.设置一个环境变量导 致默认值被清理(继承自我们的进程(process)),所以,如果需要我们不得不从我们的程序中复制环境.

在Linux上带有sudo使用 fdisk

下面的示例显示在一个Linux机器上如何使用sudo命令获取root权限来运行fdisk. 注意:这仅是一 个示例,并不适合大量输出.

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/OSX, 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.

参数含有空格(替换Shell 引用)

在Linux shell中,写引用参数是可接受的,像这:

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

并且GDB将接受3个参数(除第一个参数外,它是完整的路径到可执行文件):

  1. --batch
  2. --eval-command=info symbol 0x0000DDDD
  3. 完整的路径到myprogram

TProcess也可以传递保护空格的参数,但是它使用一个不同的引用样式。而不是仅引用参数的部分,引用参数的全 部。像这样:

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

并且也记住仅传递完整的路径.

参考关于它的这个讨论: http://bugs.freepascal.org/view.php?id=14446

LCLIntf替代方案

有时,你不需要明确地调用一个外部程序来获取你需要的功能。作为打开一个应用程序和指定的文 件来跟它相配的代替,仅要求OS来打开文档和让它使用与这些文件类型相关联的默认应用程序、下面是一些示例.

打开文档在默认应用程序中

在一些情况下,你需要使用默认相关联的应用程序打开一下文档/文件,而不是执行一个独有的程序。这依赖于运行的操作系统。Lazarus通过一个平台独立procedure(过程)OpenDocument,这将为你处理它。你的应用程序将持续运行,而不等待文档进程关闭.

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

在默认网页浏览器中打开网页

J只传递需要的URL,在某些情况下,前列的http://似乎是可选的。并且,传递一个文件名称似乎与给予 OpenDocument()使用LCLIntf的结果相同.

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

请参阅:

或,你可以使用 TProcess 像这:

uses Process;

procedure OpenWebPage(URL: string);
// Apparently you need to pass your URL inside ", like "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;

请参阅

贡献者和更改

  • 简体中文版本由 robsean 于 2019-03-07 创建。