Difference between revisions of "Executing External Programs/zh CN"
m (Bypass openurl redirection to OpenUrl) |
|||
(12 intermediate revisions by 3 users not shown) | |||
Line 55: | Line 55: | ||
一个简单的示例是 | 一个简单的示例是 | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
uses Process; | uses Process; | ||
... | ... | ||
Line 64: | Line 64: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
RunCommand的一个超载(overloaded)变体,返回程序的退出代码。RunCommandInDir 在 一个不同的目录中运行命令(设置 p.CurrentDirectory): | RunCommand的一个超载(overloaded)变体,返回程序的退出代码。RunCommandInDir 在 一个不同的目录中运行命令(设置 p.CurrentDirectory): | ||
− | <syntaxhighlight> | + | <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; var exitstatus:integer): integer; | ||
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean; | function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean; | ||
Line 74: | Line 74: | ||
虽有一个数的限制,最简单的方法来启动一个程序(模态,无管道或一些控件的形式)是简单地使用 : | 虽有一个数的限制,最简单的方法来启动一个程序(模态,无管道或一些控件的形式)是简单地使用 : | ||
− | <syntaxhighlight>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight> | + | <syntaxhighlight lang=pascal>SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);</syntaxhighlight> |
调用进程同步运行:它'挂起(hangs)',直到内部出现完成 - 但是,在你的程序中继续前,如果你想要用户来做 一些事,这可能是有用的。对于更多用途的方法,看下一关于优先考虑跨平台的'''TProcess'''部分, 或如果你仅希望瞄准Windows,你可以使用'''ShellExecute'''. | 调用进程同步运行:它'挂起(hangs)',直到内部出现完成 - 但是,在你的程序中继续前,如果你想要用户来做 一些事,这可能是有用的。对于更多用途的方法,看下一关于优先考虑跨平台的'''TProcess'''部分, 或如果你仅希望瞄准Windows,你可以使用'''ShellExecute'''. | ||
Line 82: | Line 82: | ||
== 微软Windows系统 : CreateProcess, ShellExecute 和 WinExec == | == 微软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的话语). | '''ShellExecute''' 是一个标准的微软Windows函数(ShellApi.h),在MSDN上有很好的文档(如果你发现函数不可靠,注意它们关于初始化COM的话语). | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
uses ..., ShellApi; | uses ..., ShellApi; | ||
Line 117: | Line 117: | ||
fMask选项也能使用SEE_MASK_DOENVSUBST或SEE_MASK_FLAG_NO_UI或 SEE_MASK_NOCLOSEPROCESS,等. | fMask选项也能使用SEE_MASK_DOENVSUBST或SEE_MASK_FLAG_NO_UI或 SEE_MASK_NOCLOSEPROCESS,等. | ||
− | 如果在Delphi中,你对'''文档'''使用ShellExecute,像Word文档,或URLs,看看 open* ( | + | 如果在Delphi中,你对'''文档'''使用ShellExecute,像Word文档,或URLs,看看 open* (OpenUrl等等) 函数,在lclintf中 (看这页下面的可选择部分). |
=== 使用ShellExecuteEx对于elevation/administrator权限 === | === 使用ShellExecuteEx对于elevation/administrator权限 === | ||
如果你需要执行带有administrator/elevated权限的外部程序,你可以使用'''runas'''方 法替换ShellExecuteEx函数: | 如果你需要执行带有administrator/elevated权限的外部程序,你可以使用'''runas'''方 法替换ShellExecuteEx函数: | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
uses ShellApi, ...; | uses ShellApi, ...; | ||
Line 176: | Line 176: | ||
=== 一个简单的示例 === | === 一个简单的示例 === | ||
这个示例('''这不能使用在生产中,看大量输出,或更好的, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''')仅向你显示如何运行一个外部程序: | 这个示例('''这不能使用在生产中,看大量输出,或更好的, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''')仅向你显示如何运行一个外部程序: | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
// This is a demo program that shows | // This is a demo program that shows | ||
// how to launch an external program. | // how to launch an external program. | ||
Line 224: | Line 224: | ||
'''这个示例保持简单,因此你可以从它学习。请不要在产品代码中 使用这个示例,但是使用代码在 [[#读大量输出]].''' | '''这个示例保持简单,因此你可以从它学习。请不要在产品代码中 使用这个示例,但是使用代码在 [[#读大量输出]].''' | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
// This is a | // This is a | ||
// FLAWED | // FLAWED | ||
Line 289: | Line 289: | ||
如果你想从一个外部进程中读输出,这是你需要改写到产品使用的代码. | 如果你想从一个外部进程中读输出,这是你需要改写到产品使用的代码. | ||
− | <syntaxhighlight>program LargeOutputDemo; | + | <syntaxhighlight lang=pascal>program LargeOutputDemo; |
{$mode objfpc}{$H+} | {$mode objfpc}{$H+} | ||
Line 374: | Line 374: | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
注意,上面应使用RunCommand完成: | 注意,上面应使用RunCommand完成: | ||
− | <syntaxhighlight>var s: string; | + | <syntaxhighlight lang=pascal>var s: string; |
... | ... | ||
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight> | RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight> | ||
Line 385: | Line 385: | ||
示例: | 示例: | ||
− | <syntaxhighlight>{...} | + | <syntaxhighlight lang=pascal>{...} |
AProcess := TProcess.Create(nil) | AProcess := TProcess.Create(nil) | ||
Line 402: | Line 402: | ||
你可以凭借TProcess通过在软件包中启动可执行文件开始一个'''应用程序包'''. 例如: | 你可以凭借TProcess通过在软件包中启动可执行文件开始一个'''应用程序包'''. 例如: | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal'; | AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal'; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
这将开始''Calendar'',但是窗口讲在当前应用程序的后面。为在前台获取应用程序,你可以使用 '''open'''能,带有'''-n'''参数: | 这将开始''Calendar'',但是窗口讲在当前应用程序的后面。为在前台获取应用程序,你可以使用 '''open'''能,带有'''-n'''参数: | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
AProcess.Executable:='/usr/bin/open'; | AProcess.Executable:='/usr/bin/open'; | ||
AProcess.Parameters.Add('-n'); | AProcess.Parameters.Add('-n'); | ||
Line 415: | Line 415: | ||
如果你的应用程序需要参数,你可以传送'''open'''程序的'''--args'''参数,在所有的参赛被传送到应用程序后: | 如果你的应用程序需要参数,你可以传送'''open'''程序的'''--args'''参数,在所有的参赛被传送到应用程序后: | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
AProcess.Parameters.Add('--args'); | AProcess.Parameters.Add('--args'); | ||
AProcess.Parameters.Add('argument1'); | AProcess.Parameters.Add('argument1'); | ||
Line 425: | Line 425: | ||
一般的,由你的应用程序开始的程序是一个子进程,当你的应用程序被杀死时,亦被杀死。.当你想运行 一个独立的保持运行的程序,你可以使用下面: | 一般的,由你的应用程序开始的程序是一个子进程,当你的应用程序被杀死时,亦被杀死。.当你想运行 一个独立的保持运行的程序,你可以使用下面: | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
var | var | ||
Process: TProcess; | Process: TProcess; | ||
Line 461: | Line 461: | ||
有时,你想运行更复杂的命令,命令传输它的数据到另一个命令或文件。一些东西看起来 | 有时,你想运行更复杂的命令,命令传输它的数据到另一个命令或文件。一些东西看起来 | ||
− | <syntaxhighlight>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight> | + | <syntaxhighlight lang=pascal>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight> |
或 | 或 | ||
− | <syntaxhighlight>ShellExecute('dir > output.txt');</syntaxhighlight> | + | <syntaxhighlight lang=pascal>ShellExecute('dir > output.txt');</syntaxhighlight> |
使用TProcess执行这个将被不工作,例如: | 使用TProcess执行这个将被不工作,例如: | ||
− | <syntaxhighlight>// this won't work | + | <syntaxhighlight lang=pascal>// this won't work |
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; | Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; | ||
Process.Execute;</syntaxhighlight> | Process.Execute;</syntaxhighlight> | ||
Line 482: | Line 482: | ||
不仅你可以重定向"正常"输出(也被称为stdout),但是你也可以重定向错误输出(stderr),如果你指定 poStderrToOutPut选项,像在第二个进程选项中所看. | 不仅你可以重定向"正常"输出(也被称为stdout),但是你也可以重定向错误输出(stderr),如果你指定 poStderrToOutPut选项,像在第二个进程选项中所看. | ||
− | <syntaxhighlight>program Project1; | + | <syntaxhighlight lang=pascal>program Project1; |
uses | uses | ||
Line 563: | Line 563: | ||
==== 注意 ==== | ==== 注意 ==== | ||
这个示例可能看着太夸张,尽管它能使用一个shell带有TProcess来运行"结构复杂的"命令,像: | 这个示例可能看着太夸张,尽管它能使用一个shell带有TProcess来运行"结构复杂的"命令,像: | ||
− | <syntaxhighlight>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight> | + | <syntaxhighlight lang=pascal>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight> |
但是我们的示例是更跨平台的,尽管它在Windows或Linuc等等上不需要修改. "sh"可能或不可能存在在你的 平台上,一般是仅在*nix平台上。另外,在我们的示例中,我们有更多的灵活性,尽管你可以各自地读和写从/到输入,输 出和每个标准错误(stderr),这对你的工程可能非常有利. | 但是我们的示例是更跨平台的,尽管它在Windows或Linuc等等上不需要修改. "sh"可能或不可能存在在你的 平台上,一般是仅在*nix平台上。另外,在我们的示例中,我们有更多的灵活性,尽管你可以各自地读和写从/到输入,输 出和每个标准错误(stderr),这对你的工程可能非常有利. | ||
Line 572: | Line 572: | ||
如果你可以对这使用sudo。你可以改写(adapt)下面改写自andman张贴在论坛 ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]) 上的示例. 这个示例在 <code>/root</code> 目录运行<code>ls</code>, 但是,当然可以被改写. | 如果你可以对这使用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> | + | <syntaxhighlight lang=pascal> |
program rootls; | program rootls; | ||
Line 643: | Line 643: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | 其他想法: | |
− | + | 它可能固然是明智的 来看看是否sudo确实提示输入密码。这可以始终如一的核查,通过设置环境变量SUDO_PROMPT到 我们观察读TProcess的标准输出(stdout)避免对不同地点的提示问题不同时候的一些事.设置一个环境变量导 致默认值被清理(继承自我们的进程(process)),所以,如果需要我们不得不从我们的程序中复制环境. | |
=== 在Linux上带有sudo使用 fdisk === | === 在Linux上带有sudo使用 fdisk === | ||
下面的示例显示在一个Linux机器上如何使用sudo命令获取root权限来运行fdisk. '''注意:这仅是一 个示例,并不适合大量输出.''' | 下面的示例显示在一个Linux机器上如何使用sudo命令获取root权限来运行fdisk. '''注意:这仅是一 个示例,并不适合大量输出.''' | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
program getpartitioninfo; | program getpartitioninfo; | ||
{Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc. | {Originally contributed by Lazarus forums wjackon153. Please contact him for questions, remarks etc. | ||
Line 740: | Line 740: | ||
在一些情况下,你需要使用默认相关联的应用程序打开一下文档/文件,而不是执行一个独有的程序。这依赖于运行的操作系统。Lazarus通过一个平台独立procedure(过程)'''OpenDocument''',这将为你处理它。你的应用程序将持续运行,而不等待文档进程关闭. | 在一些情况下,你需要使用默认相关联的应用程序打开一下文档/文件,而不是执行一个独有的程序。这依赖于运行的操作系统。Lazarus通过一个平台独立procedure(过程)'''OpenDocument''',这将为你处理它。你的应用程序将持续运行,而不等待文档进程关闭. | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
uses LCLIntf; | uses LCLIntf; | ||
... | ... | ||
Line 751: | Line 751: | ||
===在默认网页浏览器中打开网页=== | ===在默认网页浏览器中打开网页=== | ||
− | J只传递需要的URL,在某些情况下,前列的http:// | + | J只传递需要的URL,在某些情况下,前列的http://似乎是可选的。并且,传递一个文件名称似乎与给予 OpenDocument()使用LCLIntf的结果相同. |
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
uses LCLIntf; | uses LCLIntf; | ||
... | ... | ||
Line 759: | Line 759: | ||
请参阅: | 请参阅: | ||
− | * [[ | + | * [[OpenUrl|OpenURL reference]] |
或,你可以使用 '''TProcess''' 像这: | 或,你可以使用 '''TProcess''' 像这: | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
uses Process; | uses Process; | ||
Line 787: | Line 787: | ||
==请参阅== | ==请参阅== | ||
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess 文档] | * [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess 文档] | ||
− | * [[opendocument]] | + | * [[opendocument|OpenDocument]] |
− | * [[ | + | * [[OpenUrl]] |
* [[TProcessUTF8]] | * [[TProcessUTF8]] | ||
* [[TXMLPropStorage]] | * [[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
- RunCommand 引 用
- RunCommandInDir 引 用
在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
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个参数(除第一个参数外,它是完整的路径到可执行文件):
- --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,这将为你处理它。你的应用程序将持续运行,而不等待文档进程关闭.
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 创建。