Difference between revisions of "Executing External Programs"

From Free Pascal wiki
m (Notes: added more convincing)
m (Parameters which contain spaces (Replacing Shell Quotes))
 
(151 intermediate revisions by 27 users not shown)
Line 1: Line 1:
 
{{Executing External Programs}}
 
{{Executing External Programs}}
  
== Introduction ==
+
== Overview : Comparison ==
 +
Here are different ways available in RTL, FCL and LCL libraries on how to execute an external command/process/program.
 +
{| class="wikitable"
 +
!Method
 +
!Library
 +
!Platforms
 +
!Single Line
 +
!Features
 +
|-
 +
|[[Executing External Programs#SysUtils.ExecuteProcess|ExecuteProcess]]
 +
|RTL
 +
|Cross-Platform
 +
|Yes
 +
|Very limited, synchronous
 +
|-
 +
|[[Executing External Programs#MS Windows : CreateProcess, ShellExecute and WinExec|ShellExecute]]
 +
|WinAPI
 +
|MS Windows only
 +
|Yes
 +
|Many. Can start programs with elevation/admin permissions.
 +
|-
 +
|[[Executing External Programs#Unix fpsystem, fpexecve and shell |fpsystem, fpexecve]]
 +
|Unix
 +
|Unix only
 +
|
 +
|
 +
|-
 +
|[[Executing External Programs#TProcess|TProcess]]
 +
|FCL
 +
|Cross-Platform
 +
|No
 +
|Full
 +
|-
 +
|[[Executing External Programs#(Process.)RunCommand|RunCommand]]
 +
|FCL
 +
|Cross-Platform '''Requires FPC 2.6.2+'''
 +
|Yes
 +
|Covers common TProcess usage
 +
|-
 +
|[[Executing External Programs#LCLIntf Alternatives|OpenDocument]]
 +
|LCL
 +
|Cross-Platform
 +
|Yes
 +
|Only open document. The document would open with the application associated with that type of the document.
 +
|}
  
There are multiple ways to run an external program, but this article will only focus on one: [[doc:fcl/process/tprocess.html|TProcess]].
+
==(Process.)RunCommand==
 +
* [http://www.freepascal.org/docs-html/fcl/process/runcommand.html RunCommand] reference
 +
* [http://www.freepascal.org/docs-html/fcl/process/runcommandindir.html RunCommandInDir] reference
  
If you always use '''ShellExecute''' and/or '''WinExec''' in Delphi, then you can begin to use TProcess as an alternative in FPC/Lazarus (this is valid too if you are running Lazarus in Linux, because TProcess is cross-platform).
+
In FPC 2.6.2, some helper functions for TProcess were added to unit process based on wrappers used in the [[Projects using Lazarus#fpcup|fpcup]] project.
 +
These functions are meant for basic and intermediate use and can capture output to a single string and fully support the ''large output'' case.  
  
'''Note:''' FPC/Lazarus has support for '''ShellExecute''' and/or '''WinExec''', but this support is only in Win32. If your program is cross-platform, then use TProcess, it is the best way!
+
A simple example is:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
program project1;
 +
 
 +
{$mode objfpc}{$H+}
 +
 
 +
uses
 +
  Process;
 +
 
 +
var
 +
  s : ansistring;
 +
 
 +
begin
 +
 
 +
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
 +
  writeln(s);
 +
 
 +
end.
 +
</syntaxhighlight>
 +
 
 +
But note that not all shell "built-in" commands (eg alias) work because aliases by default are not expanded in non-interactive shells and .bashrc is not read by non-interactive shells unless you set the BASH_ENV environment variable. So, this does not produce any output:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
program project2;
 +
 
 +
{$mode objfpc}{$H+}
 +
 
 +
uses
 +
  Process;
 +
 
 +
var
 +
  s : ansistring;
 +
 
 +
begin
 +
 
 +
if RunCommand('/bin/bash',['-c','alias'],s) then
 +
  writeln(s);
 +
 
 +
end.
 +
</syntaxhighlight>
 +
 
 +
An overloaded variant of RunCommand returns the exitcode of the program. The RunCommandInDir runs the command in a different directory (sets 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>
 +
 
 +
In '''FPC 3.2.0+''' the Runcommand got additional variants that allow to override TProcessOptions and WindowOptions.
 +
 
 +
 
 +
=== Runcommand extensions ===
 +
In '''FPC 3.2.0+''' the Runcommand implementation was generalized and integrated back into TProcess to allow for quicker construction of own variants. As an example a RunCommand variant with a timeout:
 +
 
 +
 
 +
<syntaxhighlight lang="pascal"> 
 +
{$mode delphi}
 +
uses classes,sysutils,process,dateutils;
 +
Type
 +
{ TProcessTimeout }
 +
TProcessTimeout = class(TProcess)
 +
                            public
 +
                            timeoutperiod: TTime;
 +
                            timedout : boolean;
 +
                            started : TDateTime;
 +
                            procedure LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string);
 +
                          end;
 +
 +
procedure TProcessTimeout.LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string);
 +
begin
 +
  if status=RunCommandIdle then
 +
    begin
 +
      if (now-started)>timeoutperiod then
 +
        begin
 +
          timedout:=true;
 +
          Terminate(255);
 +
          exit;
 +
        end;
 +
      sleep(RunCommandSleepTime);
 +
    end;
 +
end;
 +
 +
function RunCommandTimeout(const exename:TProcessString;const commands:array of TProcessString;out outputstring:string; Options : TProcessOptions = [];SWOptions:TShowWindowOptions=swoNone;timeout:integer=60):boolean;
 +
Var
 +
    p : TProcessTimeout;
 +
    i,
 +
    exitstatus : integer;
 +
    ErrorString : String;
 +
begin
 +
  p:=TProcessTimeout.create(nil);
 +
  p.OnRunCommandEvent:=p.LocalnIdleSleep;
 +
  p.timeoutperiod:=timeout/SecsPerDay;
 +
  if Options<>[] then
 +
    P.Options:=Options - [poRunSuspended,poWaitOnExit];
 +
  p.options:=p.options+[poRunIdle]; // needed to run the RUNIDLE event. See User Changes 3.2.0
 +
 +
  P.ShowWindow:=SwOptions;
 +
  p.Executable:=exename;
 +
  if high(commands)>=0 then
 +
  for i:=low(commands) to high(commands) do
 +
    p.Parameters.add(commands[i]);
 +
  p.timedout:=false;
 +
  p.started:=now;
 +
  try
 +
    // the core loop of runcommand() variants, originally based on the "large output" scenario in the wiki, but continously expanded over 5 years.
 +
    result:=p.RunCommandLoop(outputstring,errorstring,exitstatus)=0;
 +
    if p.timedout then
 +
      result:=false;
 +
  finally
 +
    p.free;
 +
  end;
 +
  if exitstatus<>0 then result:=false;
 +
end;                                     
 +
 +
// example use:
 +
 +
var s : string;
 +
begin
 +
  for s in FileList do
 +
    begin
 +
      if not RunCommandTimeout('someexe',['-v',s,'--output','dest\'+s],err,[],swoNone,60) then
 +
          begin
 +
            // failed to run or timeout. e.g. movefile() it to a "failed" dir.
 +
        end
 +
      else
 +
        begin
 +
        // ok, move file to "done" directory.
 +
        end;
 +
    end;
 +
end;
 +
</syntaxhighlight>
  
 
== SysUtils.ExecuteProcess ==
 
== SysUtils.ExecuteProcess ==
 +
(Cross-platform)<BR>
 +
Despite a number of limitations, the simplest way to launch a program (modal, no pipes or any form of control) is to simply use :
 +
 +
<syntaxhighlight lang="pascal">
 +
SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);
 +
</syntaxhighlight>
 +
 +
The calling process runs synchronously: it 'hangs' until the external program has finished - but this may be useful if you require the user to do something before continuing in your application. For a more versatile approach, see the section about the preferred cross-platform '''RunCommand''' or other '''TProcess''' functionality, or if you only wish to target Windows you may use '''ShellExecute'''.
 +
 +
* [http://www.freepascal.org/docs-html/rtl/sysutils/executeprocess.html ExecuteProcess reference]
 +
 +
== MS Windows : CreateProcess, ShellExecute and WinExec ==
 +
 +
{{Note|While FPC/Lazarus has support for '''CreateProcess''', '''ShellExecute''' and/or '''WinExec''', this support is only in Win32/64. If your program is cross-platform, consider using '''RunCommand''' or '''TProcess'''.}}
 +
{{Note|WinExec is a 16-bit call that has been deprecated for years in the Windows API. In recent versions of FPC it generates a warning.}}
 +
 +
'''ShellExecute''' is a standard MS Windows function (ShellApi.h) with good [http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85).aspx documentation on MSDN] (note their remarks about initialising COM if you find the function unreliable).
 +
 +
<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>
 +
 +
There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.
 +
 +
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:
 +
 +
<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 and shell ==
 +
 +
These functions are platform dependent.
 +
 +
* [http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html fpsystem reference]
 +
* [http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html fpexecve reference]
  
The simplest way if you don't need pipes or any form of control is to simply use SysUtils.ExecuteProcess('/full/path/to/binary',['arg1','arg2']);
+
Linux.Shell/Unix.Shell was an 1.0.x equivalent to fpsystem with less narrowly defined error handling, and after a decade of deprecation it was finally removed. In nearly all cases it can be replaced by fpsystem that features more POSIX like errorhandling.
  
 
== TProcess ==
 
== TProcess ==
 +
 
You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:
 
You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:
  
*Platform Independent
+
* Platform Independent
*Capable of reading from stdout and writing to stdin.
+
* Capable of reading from stdout and writing to stdin.
 +
* Possible to wait for a command to finish or let it run while your program moves on.
  
Note: TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..
+
Important notes:
 +
* TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..
 +
* Presumably on Linux/Unix: you '''must''' specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] from the [[doc:lcl/fileutil/index.html|FileUtil]] unit of the LCL.
 +
* On Windows, if the command is in the path, you don't need to specify the full path.
 +
* [[doc:fcl/process/tprocess.html|TProcess reference]]
  
Important: You must specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function [[doc:lcl/fileutil/finddefaultexecutablepath.html|FindDefaultExecutablePath]] from the [[doc:lcl/fileutil/index.html|FileUtil]] unit of the LCL.
+
=== The Simplest Example ===
 +
 
 +
A lot of typical cases have been prepared in the [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]] functions. Before you start copy and paste the examples below, check them out first.
  
 
=== A Simple Example ===
 
=== A Simple Example ===
<pascal>
+
This example ('''that shouldn't be used in production, see Large Output or, better, [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]''') just shows you how to run an external program, nothing more:
// This is a demo program that shows how to launch
+
 
// an external program.
+
<syntaxhighlight lang="pascal">
program launchprogram;
+
// This is a demo program that shows
 +
// how to launch an external program.
 +
program launchprogram;
 
   
 
   
// Here we include files that have useful functions
+
// Here we include files that have useful functions
// and procedures we will need.
+
// and procedures we will need.
uses  
+
uses  
  Classes, SysUtils, Process;
+
  Classes, SysUtils, Process;
 
   
 
   
// This is defining the var "AProcess" as a variable  
+
// This defines the var "AProcess" as a variable  
// of the type "TProcess"
+
// of the type "TProcess"
var  
+
var  
  AProcess: TProcess;
+
  AProcess: TProcess;
 
   
 
   
// This is where our program starts to run
+
// This is where our program starts to run
begin
+
begin
  // Now we will create the TProcess object, and
+
  // Now we will create the TProcess object, and
  // assign it to the var AProcess.
+
  // assign it to the var AProcess.
  AProcess := TProcess.Create(nil);
+
  AProcess := TProcess.Create(nil);
 
   
 
   
  // Tell the new AProcess what the command to execute is.
+
  // Tell the new AProcess what the command to execute is.
  // Let's use the FreePascal compiler
+
  // Let's use the Free Pascal compiler (i386 version that is)
  AProcess.CommandLine := 'ppc386 -h';
+
  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
+
  // We will define an option for when the program
  // is run. This option will make sure that our program
+
  // is run. This option will make sure that our program
  // does not continue until the program we will launch
+
  // does not continue until the program we will launch
  // has stopped running.                vvvvvvvvvvvvvv
+
  // has stopped running.                vvvvvvvvvvvvvv
  AProcess.Options := AProcess.Options + [poWaitOnExit];
+
  AProcess.Options := AProcess.Options + [poWaitOnExit];
 
   
 
   
  // Now that AProcess knows what the commandline is
+
  // Now let AProcess run the program
  // we will run it.
+
  AProcess.Execute;
  AProcess.Execute;
 
 
   
 
   
  // This is not reached until ppc386 stops running.
+
  // This is not reached until ppc386 stops running.
  AProcess.Free;   
+
  AProcess.Free;   
end.
+
end.</syntaxhighlight>
</pascal>
+
That's it! You have just learned to run an external program from inside your own program.
  
That's it! You have just learned how to run an external program from inside your own program.
+
=== An improved example (but not correct yet)===
 
 
=== An Improved Example ===
 
 
That's nice, but how do I read the Output of a program that I have run?
 
That's nice, but how do I read the Output of a program that I have run?
  
 
Well, let's expand our example a little and do just that:
 
Well, let's expand our example a little and do just that:
 +
'''This example is kept simple so you can learn from it. Please don't use this example in production code, but use the code in [[#Reading large output]].'''
  
<delphi>
+
<syntaxhighlight lang="pascal">
// This is a demo program that shows how to launch
+
// This is a  
// an external program and read from it's output.
+
// FLAWED
program launchprogram;
+
// 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
+
// Here we include files that have useful functions
// and procedures we will need.
+
// and procedures we will need.
uses  
+
uses  
  Classes, SysUtils, Process;
+
  Classes, SysUtils, Process;
 
   
 
   
// This is defining the var "AProcess" as a variable  
+
// This is defining the var "AProcess" as a variable  
// of the type "TProcess"
+
// of the type "TProcess"
// Also now we are adding a TStringList to store the  
+
// Also now we are adding a TStringList to store the  
// data read from the programs output.
+
// data read from the programs output.
var  
+
var  
  AProcess: TProcess;
+
  AProcess: TProcess;
  AStringList: TStringList;
+
  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);
 
   
 
   
// This is where our program starts to run
+
  // Tell the new AProcess what the command to execute is.
begin
+
  AProcess.Executable := '/usr/bin/ppc386';
  // Now we will create the TProcess object, and
+
  AProcess.Parameters.Add('-h');
  // assign it to the var AProcess.
+
 
  AProcess := TProcess.Create(nil);
+
  // 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];
 
   
 
   
  // Create the TStringList object.
+
  // Now that AProcess knows what the commandline is it can be run.
  AStringList := TStringList.Create;
+
  AProcess.Execute;
+
 
  // Tell the new AProcess what the command to execute is.
+
  // After AProcess has finished, the rest of the program will be executed.
  // Let's use the FreePascal compiler
 
  AProcess.CommandLine := 'ppc386 -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  
 
  // we will run it.
 
  AProcess.Execute;
 
 
 
  // This is not reached until ppc386 stops running.
 
 
   
 
   
  // Now read the output of the program we just ran
+
  // Now read the output of the program we just ran into a TStringList.
  // into the TStringList.
+
  AStringList := TStringList.Create;
  AStringList.LoadFromStream(AProcess.Output);
+
  AStringList.LoadFromStream(AProcess.Output);
 
    
 
    
  // Save the output to a file.
+
  // Save the output to a file and clean up the TStringList.
  AStringList.SaveToFile('output.txt');
+
  AStringList.SaveToFile('output.txt');
 +
  AStringList.Free;
 
   
 
   
  // Now that the file is saved we can free the
+
  // Now that the output from the process is processed, it can be freed.
  // TStringList and the TProcess.
+
  AProcess.Free;   
  AStringList.Free;
+
end.</syntaxhighlight>
  AProcess.Free;   
 
end.
 
</delphi>
 
  
 
=== Reading large output ===
 
=== Reading large output ===
In the previous example we waited until the program exited. Then we read, what the program has written to its output. But suppose the program writes a lot of data to the output, the pipe becomes full and the called progam waits until the pipe has been read from. But the calling program doesn't read from it, until the called program has ended. A dead lock occurs.
+
In the previous example we waited until the program exited. Then we read what the program has written to its output.
 +
 
 +
Suppose the program writes a lot of data to the output. Then the output pipe becomes full and the called progam waits until the pipe has been read from.  
 +
 
 +
But the calling program doesn't read from it until the called program has ended. A deadlock occurs.
 +
 
 +
The following example therefore doesn't use poWaitOnExit, but reads from the output while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.
 +
 
 +
If you want to read output from an external process, and can't use RunCommand, this is the code you should as a base for production use. If you are using '''FPC 3.2.0+''' a parametrisable form of this loop is available as the method '''RunCommandLoop''' in TProcess. An event '''OnRunCommandEvent''' can be hooked to further modify the behaviour.  
  
The following example therefore doesn't use poWaitOnExit, but reads from the output, while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.
+
<syntaxhighlight lang="pascal">program LargeOutputDemo;
  
<delphi> program procoutlarge;
+
{$mode objfpc}{$H+}
{
+
 
    Copyright (c) 2004 by Marc Weustink
+
uses
+
  Classes, SysUtils, Process; // Process is the unit that holds TProcess
    This example is creeated in the hope that it will be useful,
+
 
    but WITHOUT ANY WARRANTY; without even the implied warranty of
+
const
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
  BUF_SIZE = 2048; // Buffer size for reading the output in chunks
}
+
 
+
var
uses
+
  AProcess    : TProcess;
  Classes, Process, SysUtils;
+
  OutputStream : TStream;
+
  BytesRead   : longint;
const
+
  Buffer      : array[1..BUF_SIZE] of byte;
  READ_BYTES = 2048;
+
 
 
+
begin
var
+
  // Set up the process; as an example a recursive directory search is used
  S: TStringList;
+
  // because that will usually result in a lot of data.
  M: TMemoryStream;
+
  AProcess := TProcess.Create(nil);
   P: TProcess;
+
 
  n: LongInt;
+
  // The commands for Windows and *nix are different hence the $IFDEFs
  BytesRead: LongInt;
+
  {$IFDEF Windows}
+
    // In Windows the dir command cannot be used directly because it's a build-in
begin
+
    // shell command. Therefore cmd.exe and the extra parameters are needed.
  // We cannot use poWaitOnExit here since we don't
+
    AProcess.Executable := 'c:\windows\system32\cmd.exe';
  // know the size of the output. On Linux the size of the
+
    AProcess.Parameters.Add('/c');
  // output pipe is 2 kB. If the output data is more, we
+
    AProcess.Parameters.Add('dir /s c:\windows');
  // need to read the data. This isn't possible since we are
+
  {$ENDIF Windows}
  // waiting. So we get a deadlock here.
+
 
  //
+
  {$IFDEF Unix}
  // A temp Memorystream is used to buffer the output
+
    AProcess.Executable := '/bin/ls';
 
+
    AProcess.Parameters.Add('--recursive');
  M := TMemoryStream.Create;
+
    AProcess.Parameters.Add('--all');
  BytesRead := 0;
+
    AProcess.Parameters.Add('-l');
+
  {$ENDIF Unix}
  P := TProcess.Create(nil);
+
 
  P.CommandLine := 'ppc386 -va bogus.pp';
+
  // Process option poUsePipes has to be used so the output can be captured.
  P.Options := [poUsePipes];
+
  // Process option poWaitOnExit can not be used because that would block
  WriteLn('-- executing --');
+
  // this program, preventing it from reading the output data of the process.
  P.Execute;
+
  AProcess.Options := [poUsePipes];
  while P.Running do
+
 
  begin         
+
  // Start the process (run the dir/ls command)
    // make sure we have room
+
  AProcess.Execute;
    M.SetSize(BytesRead + READ_BYTES);
+
 
   
+
  // Create a stream object to store the generated output in. This could
    // try reading it
+
  // also be a file stream to directly save the output to disk.
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
+
  OutputStream := TMemoryStream.Create;
    if n > 0
+
 
    then begin
+
  // All generated output from AProcess is read in a loop until no more data is available
      Inc(BytesRead, n);
+
  repeat
      Write('.')
+
    // Get the new data from the process to a maximum of the buffer size that was allocated.
    end
+
    // Note that all read(...) calls will block except for the last one, which returns 0 (zero).
    else begin   
+
    BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
      // no data, wait 100 ms
+
 
      Sleep(100);  
+
    // Add the bytes that were read to the stream for later usage
    end;
+
    OutputStream.Write(Buffer, BytesRead)
  end;
+
 
  // read last part
+
  until BytesRead = 0;  // Stop if no more data is available
  repeat
+
 
    // make sure we have room
+
  // The process has finished so it can be cleaned up
    M.SetSize(BytesRead + READ_BYTES);
+
  AProcess.Free;
    // try reading it
+
 
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
+
  // Now that all data has been read it can be used; for example to save it to a file on disk
    if n > 0
+
  with TFileStream.Create('output.txt', fmCreate) do
    then begin
+
  begin
      Inc(BytesRead, n);
+
    OutputStream.Position := 0; // Required to make sure all data is copied from the start
      Write('.');
+
    CopyFrom(OutputStream, OutputStream.Size);
    end;
+
    Free
  until n <= 0;
+
  end;
  if BytesRead > 0 then WriteLn;
+
 
  M.SetSize(BytesRead);  
+
  // Or the data can be shown on screen
  WriteLn('-- executed --');
+
  with TStringList.Create do
 
+
  begin
  S := TStringList.Create;
+
    OutputStream.Position := 0; // Required to make sure all data is copied from the start
  S.LoadFromStream(M);
+
    LoadFromStream(OutputStream);
  WriteLn('-- linecount = ', S.Count, ' --');
+
    writeln(Text);
  for n := 0 to S.Count - 1 do
+
    writeln('--- Number of lines = ', Count, '----');
  begin
+
    Free
    WriteLn('| ', S[n]);
+
  end;
  end;
+
 
  WriteLn('-- end --');
+
  // Clean up
  S.Free;
+
  OutputStream.Free;
  P.Free;
+
end.</syntaxhighlight>
  M.Free;
+
Note that the above could also be accomplished by using RunCommand:
end.</delphi>
+
 
 +
<syntaxhighlight lang="pascal">
 +
var s: string;
 +
...
 +
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight>
  
 
=== Using input and output of a TProcess ===
 
=== Using input and output of a TProcess ===
See processdemo example in the [https://lazarus-ccr.svn.sourceforge.net/svnroot/lazarus-ccr/examples/process Lazarus-CCR SVN].
+
See processdemo example in the [https://sourceforge.net/p/lazarus-ccr/svn/HEAD/tree/examples/process Lazarus-CCR SVN].
  
 
=== Hints on the use of TProcess ===
 
=== Hints on the use of TProcess ===
If you are creating a cross-platform program, you can change commandline according to the OS, using directives "{$IFDEF}s" and "{$ENDIF}s".
+
When creating a cross-platform program, the OS-specific executable name can be set using directives "{$IFDEF}" and "{$ENDIF}".
  
 
Example:
 
Example:
<delphi> {...}
+
<syntaxhighlight lang="pascal">
  AProcess:TProcess.Create(nil)
+
{...}
  {$IFDEF WIN32}
+
AProcess := TProcess.Create(nil)
  AProcess.CommandLine := 'calc.exe'; //Windows Calc
+
 
  {$ENDIF}
+
{$IFDEF WIN32}
  {$IFDEF LINUX}
+
  AProcess.Executable := 'calc.exe';  
  AProcess.CommandLine := 'kcalc'; //KDE Calc
+
{$ENDIF}
  {$ENDIF}
+
 
  AProcess.Execute; //in alternative, you can use AProcess.Active:=True
+
{$IFDEF LINUX}
  {...}</delphi>
+
  AProcess.Executable := FindDefaultExecutablePath('kcalc');
 +
{$ENDIF}
 +
 
 +
AProcess.Execute;
 +
{...}</syntaxhighlight>
 +
 
 +
=== macOS show application bundle in foreground ===
 +
 
 +
You can start an '''application bundle''' via TProcess by starting the executable within the bundle. For example:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';
 +
</syntaxhighlight>
 +
 
 +
This will start the ''Calendar'', but the window will be behind the current application.
 +
To get the application in the foreground you can use the '''open''' utility with the '''-n''' parameter:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
AProcess.Executable:='/usr/bin/open';
 +
AProcess.Parameters.Add('-n');
 +
  AProcess.Parameters.Add('-a'); // optional: specifies the application to use; only searches the Application directories
 +
AProcess.Parameters.Add('-W'); // optional: open waits until the applications it opens (or were already open) have exited
 +
AProcess.Parameters.Add('Pages.app'); // including .app is optional
 +
</syntaxhighlight>
 +
 
 +
If your application needs parameters, you can pass '''open''' the '''--args''' parameter, after which all parameters are passed to the application:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
AProcess.Parameters.Add('--args');
 +
AProcess.Parameters.Add('argument1');
 +
AProcess.Parameters.Add('argument2');
 +
</syntaxhighlight>
 +
 
 +
See also: [[macOS_Open_Sesame|macOS open command]].
 +
 
 +
=== Run detached program ===
 +
 
 +
Normally a program started by your application is a child process and is killed, when your application is killed. When you want to run a standalone program that keeps running, you can use the following:
 +
 
 +
<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>
  
 
=== Example of "talking" with aspell process ===
 
=== Example of "talking" with aspell process ===
  
Inside [http://pasdoc.sourceforge.net/ pasdoc] source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:
+
Inside [https://github.com/pasdoc/pasdoc/wiki pasdoc] source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:
  
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit] implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.
+
* [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas unit] implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.
  
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units] implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.
+
* [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas units] implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.
  
 
Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.
 
Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.
Line 249: Line 592:
 
=== Replacing shell operators like "| < >" ===
 
=== Replacing shell operators like "| < >" ===
  
Sometimes you want to run a more complicated command that pipes it's data to another command or to a file.
+
Sometimes you want to run a more complicated command that pipes its data to another command or to a file.
 
Something like  
 
Something like  
  ShellExecute('firstcommand.exe | secondcommand.exe');
+
<syntaxhighlight lang="pascal">
 +
ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight>
 
or
 
or
  ShellExecute('dir > output.txt');
+
<syntaxhighlight lang="pascal">ShellExecute('dir > output.txt');</syntaxhighlight>
  
 
Executing this with TProcess will not work. i.e:
 
Executing this with TProcess will not work. i.e:
  // this won't work
+
 
  Process.CommandLine := 'firstcommand.exe | secondcommand.exe';  
+
<syntaxhighlight lang="pascal">
  Process.Execute;
+
// this won't work
 +
Process.CommandLine := 'firstcommand.exe | secondcommand.exe';  
 +
Process.Execute;</syntaxhighlight>
 +
 
 
==== Why using special operators to redirect output doesn't work ====
 
==== Why using special operators to redirect output doesn't work ====
 
TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one.
 
TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one.
It is possible to redirect output however just the way you wanted. See the next section.
+
It is possible to redirect output however just the way you wanted. See the [[Executing_External_Programs#How_to_redirect_output_with_TProcess |next section]].
  
 
=== How to redirect output with TProcess ===
 
=== How to redirect output with TProcess ===
Line 268: Line 615:
  
 
Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example [[Executing_External_Programs#Reading_large_output | Reading Large Output ]]
 
Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example [[Executing_External_Programs#Reading_large_output | Reading Large Output ]]
<delphi>
+
 
  program Project1;
+
Not only can you redirect the "normal" output (also known as stdout), but you can also redirect the error output (stderr), if you specify the poStderrToOutPut option, as seen in the options for the second process.
 +
 
 +
<syntaxhighlight lang="pascal">
 +
program Project1;
 
    
 
    
  uses
+
uses
    Classes, sysutils, process;
+
  Classes, sysutils, process;
 
    
 
    
  var
+
var
    FirstProcess,
+
  FirstProcess,
    SecondProcess: TProcess;
+
  SecondProcess: TProcess;
    Buffer: array[0..127] of char;
+
  Buffer: array[0..127] of char;
    ReadCount: Integer;
+
  ReadCount: Integer;
    ReadSize: Integer;
+
  ReadSize: Integer;
  begin
+
begin
    FirstProcess  := TProcess.Create(nil);
+
  FirstProcess  := TProcess.Create(nil);
    SecondProcess := TProcess.Create(nil);
+
  SecondProcess := TProcess.Create(nil);
 +
 +
  FirstProcess.Options    := [poUsePipes];
 +
  FirstProcess.Executable  := 'pwd';  
 
    
 
    
    FirstProcess.Options := [poUsePipes];
+
  SecondProcess.Options   := [poUsePipes,poStderrToOutPut];
    SecondProcess.Options := [poUsePipes,poStderrToOutPut];
+
  SecondProcess.Executable := 'grep';
 +
  SecondProcess.Parameters.Add(DirectorySeparator+ ' -');  
 +
  // this would be the same as "pwd | grep / -"
 
    
 
    
    FirstProcess.CommandLine  := 'pwd';
+
  FirstProcess.Execute;
    SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -';  
+
  SecondProcess.Execute;
    // this would be the same as "pwd | grep / -"
 
 
    
 
    
    FirstProcess.Execute;
+
   while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
    SecondProcess.Execute;
+
  begin
    
+
    if FirstProcess.Output.NumBytesAvailable > 0 then
    while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
 
 
     begin
 
     begin
       if FirstProcess.Output.NumBytesAvailable > 0 then
+
       // make sure that we don't read more data than we have allocated
      begin
+
      // in the buffer
        // make sure that we don't read more data than we have allocated
+
      ReadSize := FirstProcess.Output.NumBytesAvailable;
        // in the buffer
+
      if ReadSize > SizeOf(Buffer) then
        ReadSize := FirstProcess.Output.NumBytesAvailable;
+
        ReadSize := SizeOf(Buffer);
        if ReadSize > SizeOf(Buffer) then
+
      // now read the output into the buffer
          ReadSize := SizeOf(Buffer);
+
      ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
        // now read the output into the buffer
+
      // and write the buffer to the second process
        ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
+
      SecondProcess.Input.Write(Buffer[0], ReadCount);
        // and write the buffer to the second process
 
        SecondProcess.Input.Write(Buffer[0], ReadCount);
 
 
    
 
    
        // if SecondProcess writes much data to it's Output then  
+
      // if SecondProcess writes much data to it's Output then  
        // we should read that data here to prevent a deadlock
+
      // we should read that data here to prevent a deadlock
        // see the previous example "Reading Large Output"
+
      // see the previous example "Reading Large Output"
      end;
 
 
     end;
 
     end;
    // Close the input on the SecondProcess
+
  end;
    // so it finishes processing it's data
+
  // Close the input on the SecondProcess
    SecondProcess.CloseInput;
+
  // 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
+
   // and wait for it to complete
    // it's input is closed and the following line may loop forever
+
  // be carefull what command you run because it may not exit when
    while SecondProcess.Running do
+
  // it's input is closed and the following line may loop forever
      Sleep(1);
+
  while SecondProcess.Running do
    // that's it! the rest of the program is just so the example
+
    Sleep(1);
    // is a little 'useful'
+
  // 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:');
 
    
 
    
    // we will reuse Buffer to output the SecondProcess's
+
   // free our process objects
    // output to *this* programs stdout
+
  FirstProcess.Free;
    WriteLn('Grep output Start:');
+
  SecondProcess.Free;
    ReadSize := SecondProcess.Output.NumBytesAvailable;
+
end.</syntaxhighlight>
    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.
 
</delphi>
 
  
 
That's it. Now you can redirect output from one program to another.
 
That's it. Now you can redirect output from one program to another.
 +
 
==== Notes ====
 
==== Notes ====
 
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:
 
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:
  Process.Commandline := 'sh -c "pwd | grep / -"';
+
<syntaxhighlight lang="pascal">Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight>
 +
 
 
But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.
 
But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.
 +
 +
===Redirecting input and output and running under root===
 +
A common problem on Unixes (FreeBSD, macOS) and Linux is that you want to execute some program under the root account (or, more generally, another user account). An example would be running the ''ping'' command.
 +
 +
If you can use sudo for this, you could adapt the following example adapted from one posted by andyman on the forum ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). This sample runs <code>ls</code> on the <code>/root</code> 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. [http://lazarus.freepascal.org/index.php/topic,14479.0.html 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.
 +
 +
<syntaxhighlight lang="pascal">
 +
program rootls;
 +
 +
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,
 +
calling sudo on FreeBSD/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.
 +
</syntaxhighlight>
 +
 +
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. '''Note: this is an example only, and does not cater for large output.'''
 +
 +
<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/FreeBSD/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.     
 +
</syntaxhighlight>
 +
 +
===Parameters which contain spaces (Replacing Shell Quotes)===
 +
 +
In the Linux shell it is possible to write quoted arguments like this:
 +
 +
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram
 +
 +
And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):
 +
#--batch
 +
#--eval-command=info symbol 0x0000DDDD
 +
#the full path to myprogram
 +
 +
The best solution to avoid complicated quoting is to to switch to TProcess.Parameters.Add instead of setting the Commandline for non trivial cases e.g.
 +
 +
  AProcess.Executable:='/usr/bin/gdb';
 +
  AProcess.Parameters.Add('--batch');
 +
  AProcess.Parameters.Add('--eval-command=info symbol 0x0000DDDD'); // note the absence of quoting here
 +
  AProcess.Parameters.Add('/home/me/myprogram');
 +
 +
And also remember to only pass full paths.
 +
 +
TProcess.Commandline however does support some basic quoting for parameters with quoting. Quote the ''whole'' parameter containing spaces with double quotes. Like this:
 +
 +
AProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';
 +
 +
 +
The .CommandLine property is [http://bugs.freepascal.org/view.php?id=14446 deprecated] and bugreports to support more complicated quoting cases won't accepted.
 +
 +
==LCLIntf Alternatives==
 +
Sometimes, you don't need to explicitly call an external program to get the functionality you need. Instead of opening an application and specifying the document to go with it, just ask the OS to open the document and let it use the default application associated with that file type. Below are some examples.
 +
 +
===Open document in default application===
 +
 +
In some situations you need to open some document/file using default associated application rather than execute a particular program.
 +
This depends on running operating system. Lazarus provides a platform independent procedure '''OpenDocument''' which will handle it for you. Your application will continue running without waiting for the document process to close.
 +
 +
<syntaxhighlight lang="pascal">
 +
uses LCLIntf;
 +
...
 +
OpenDocument('manual.pdf'); 
 +
...
 +
</syntaxhighlight>
 +
 +
* [[opendocument|OpenDocument reference]]
 +
 +
===Open web page in default web browser===
 +
 +
Just pass the URL required, the leading http:// appears to be optional under certain circumstances.
 +
Also, passing a filename appears to give the same results as OpenDocument()
 +
<syntaxhighlight lang="pascal">
 +
uses LCLIntf;
 +
...
 +
OpenURL('www.lazarus.freepascal.org/');
 +
</syntaxhighlight>
 +
 +
See also:
 +
* [[openurl|OpenURL reference]]
 +
 +
Or, you could use '''TProcess''' like this:
 +
 +
<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>
  
 
==See also==
 
==See also==
 
+
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html TProcess documentation]
 +
* [[opendocument]]
 +
* [[openurl]]
 +
* [[TProcessUTF8]]
 
* [[TXMLPropStorage]]
 
* [[TXMLPropStorage]]
 
+
* [[Webbrowser]]
[[Category:Tutorials]]
 

Latest revision as of 10:44, 12 September 2020

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

Overview : Comparison

Here are different ways available in RTL, FCL and LCL libraries on how to execute an external command/process/program.

Method Library Platforms Single Line Features
ExecuteProcess RTL Cross-Platform Yes Very limited, synchronous
ShellExecute WinAPI MS Windows only Yes Many. Can start programs with elevation/admin permissions.
fpsystem, fpexecve Unix Unix only
TProcess FCL Cross-Platform No Full
RunCommand FCL Cross-Platform Requires FPC 2.6.2+ Yes Covers common TProcess usage
OpenDocument LCL Cross-Platform Yes Only open document. The document would open with the application associated with that type of the document.

(Process.)RunCommand

In FPC 2.6.2, some helper functions for TProcess were added to unit process based on wrappers used in the fpcup project. These functions are meant for basic and intermediate use and can capture output to a single string and fully support the large output case.

A simple example is:

program project1;

{$mode objfpc}{$H+}

uses 
  Process;

var 
  s : ansistring;

begin

if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
   writeln(s); 

end.

But note that not all shell "built-in" commands (eg alias) work because aliases by default are not expanded in non-interactive shells and .bashrc is not read by non-interactive shells unless you set the BASH_ENV environment variable. So, this does not produce any output:

program project2;

{$mode objfpc}{$H+}

uses 
  Process;

var 
  s : ansistring;

begin

if RunCommand('/bin/bash',['-c','alias'],s) then
  writeln(s); 

end.

An overloaded variant of RunCommand returns the exitcode of the program. The RunCommandInDir runs the command in a different directory (sets 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;

In FPC 3.2.0+ the Runcommand got additional variants that allow to override TProcessOptions and WindowOptions.


Runcommand extensions

In FPC 3.2.0+ the Runcommand implementation was generalized and integrated back into TProcess to allow for quicker construction of own variants. As an example a RunCommand variant with a timeout:


  
{$mode delphi}
uses classes,sysutils,process,dateutils;
Type
{ TProcessTimeout }
 TProcessTimeout = class(TProcess)
                            public
                            timeoutperiod: TTime;
                            timedout : boolean;
                            started : TDateTime;
                            procedure LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string);
                          end;
 
procedure TProcessTimeout.LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string);
begin
   if status=RunCommandIdle then
    begin
      if (now-started)>timeoutperiod then
         begin
           timedout:=true;
           Terminate(255);
           exit;
         end;
      sleep(RunCommandSleepTime);
    end;
end;
 
function RunCommandTimeout(const exename:TProcessString;const commands:array of TProcessString;out outputstring:string; Options : TProcessOptions = [];SWOptions:TShowWindowOptions=swoNone;timeout:integer=60):boolean;
Var
    p : TProcessTimeout;
    i,
    exitstatus : integer;
    ErrorString : String;
begin
  p:=TProcessTimeout.create(nil);
  p.OnRunCommandEvent:=p.LocalnIdleSleep;
  p.timeoutperiod:=timeout/SecsPerDay;
  if Options<>[] then
    P.Options:=Options - [poRunSuspended,poWaitOnExit];
  p.options:=p.options+[poRunIdle]; // needed to run the RUNIDLE event. See User Changes 3.2.0
 
  P.ShowWindow:=SwOptions;
  p.Executable:=exename;
  if high(commands)>=0 then
   for i:=low(commands) to high(commands) do
     p.Parameters.add(commands[i]);
  p.timedout:=false;
  p.started:=now;
  try
    // the core loop of runcommand() variants, originally based on the "large output" scenario in the wiki, but continously expanded over 5 years.
    result:=p.RunCommandLoop(outputstring,errorstring,exitstatus)=0;
    if p.timedout then
      result:=false;
  finally
    p.free;
  end;
  if exitstatus<>0 then result:=false;
end;                                      
 
// example use:
 
var s : string;
begin
  for s in FileList do
    begin
       if not RunCommandTimeout('someexe',['-v',s,'--output','dest\'+s],err,[],swoNone,60) then
          begin
            // failed to run or timeout. e.g. movefile() it to a "failed" dir.
         end
      else
        begin
         // ok, move file to "done" directory.
        end;
    end;
end;

SysUtils.ExecuteProcess

(Cross-platform)
Despite a number of limitations, the simplest way to launch a program (modal, no pipes or any form of control) is to simply use :

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

The calling process runs synchronously: it 'hangs' until the external program has finished - but this may be useful if you require the user to do something before continuing in your application. For a more versatile approach, see the section about the preferred cross-platform RunCommand or other TProcess functionality, or if you only wish to target Windows you may use ShellExecute.

MS Windows : CreateProcess, ShellExecute and WinExec

Note-icon.png

Note: While FPC/Lazarus has support for CreateProcess, ShellExecute and/or WinExec, this support is only in Win32/64. If your program is cross-platform, consider using RunCommand or TProcess.

Note-icon.png

Note: WinExec is a 16-bit call that has been deprecated for years in the Windows API. In recent versions of FPC it generates a warning.

ShellExecute is a standard MS Windows function (ShellApi.h) with good documentation on MSDN (note their remarks about initialising COM if you find the function unreliable).

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;

There is also ShellExecuteExW as a WideChar version, and ShellExecuteExA is AnsiChar.

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 and shell

These functions are platform dependent.

Linux.Shell/Unix.Shell was an 1.0.x equivalent to fpsystem with less narrowly defined error handling, and after a decade of deprecation it was finally removed. In nearly all cases it can be replaced by fpsystem that features more POSIX like errorhandling.

TProcess

You can use TProcess to launch external programs. Some of the benefits of using TProcess are that it is:

  • Platform Independent
  • Capable of reading from stdout and writing to stdin.
  • Possible to wait for a command to finish or let it run while your program moves on.

Important notes:

  • TProcess is not a terminal/shell! You cannot directly execute scripts or redirect output using operators like "|", ">", "<", "&" etc. It is possible to obtain the same results with TProcess using pascal, some examples are below..
  • Presumably on Linux/Unix: you must specify the full path to the executable. For example '/bin/cp' instead of 'cp'. If the program is in the standard PATH then you can use the function FindDefaultExecutablePath from the FileUtil unit of the LCL.
  • On Windows, if the command is in the path, you don't need to specify the full path.
  • TProcess reference

The Simplest Example

A lot of typical cases have been prepared in the Runcommand functions. Before you start copy and paste the examples below, check them out first.

A Simple Example

This example (that shouldn't be used in production, see Large Output or, better, Runcommand) just shows you how to run an external program, nothing more:

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

That's it! You have just learned to run an external program from inside your own program.

An improved example (but not correct yet)

That's nice, but how do I read the Output of a program that I have run?

Well, let's expand our example a little and do just that: This example is kept simple so you can learn from it. Please don't use this example in production code, but use the code in #Reading large output.

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

Reading large output

In the previous example we waited until the program exited. Then we read what the program has written to its output.

Suppose the program writes a lot of data to the output. Then the output pipe becomes full and the called progam waits until the pipe has been read from.

But the calling program doesn't read from it until the called program has ended. A deadlock occurs.

The following example therefore doesn't use poWaitOnExit, but reads from the output while the program is still running. The output is stored in a memory stream, that can be used later to read the output into a TStringList.

If you want to read output from an external process, and can't use RunCommand, this is the code you should as a base for production use. If you are using FPC 3.2.0+ a parametrisable form of this loop is available as the method RunCommandLoop in TProcess. An event OnRunCommandEvent can be hooked to further modify the behaviour.

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.

Note that the above could also be accomplished by using RunCommand:

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

Using input and output of a TProcess

See processdemo example in the Lazarus-CCR SVN.

Hints on the use of TProcess

When creating a cross-platform program, the OS-specific executable name can be set using directives "{$IFDEF}" and "{$ENDIF}".

Example:

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

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

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

AProcess.Execute;
{...}

macOS show application bundle in foreground

You can start an application bundle via TProcess by starting the executable within the bundle. For example:

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

This will start the Calendar, but the window will be behind the current application. To get the application in the foreground you can use the open utility with the -n parameter:

 AProcess.Executable:='/usr/bin/open';
 AProcess.Parameters.Add('-n');
 AProcess.Parameters.Add('-a'); // optional: specifies the application to use; only searches the Application directories
 AProcess.Parameters.Add('-W'); // optional: open waits until the applications it opens (or were already open) have exited 
 AProcess.Parameters.Add('Pages.app'); // including .app is optional

If your application needs parameters, you can pass open the --args parameter, after which all parameters are passed to the application:

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

See also: macOS open command.

Run detached program

Normally a program started by your application is a child process and is killed, when your application is killed. When you want to run a standalone program that keeps running, you can use the following:

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;

Example of "talking" with aspell process

Inside pasdoc source code you can find two units that perform spell-checking by "talking" with running aspell process through pipes:

  • PasDoc_ProcessLineTalk.pas unit implements TProcessLineTalk class, descendant of TProcess, that can be easily used to talk with any process on a line-by-line basis.
  • PasDoc_Aspell.pas units implements TAspellProcess class, that performs spell-checking by using underlying TProcessLineTalk instance to execute aspell and communicate with running aspell process.

Both units are rather independent from the rest of pasdoc sources, so they may serve as real-world examples of using TProcess to run and communicate through pipes with other program.

Replacing shell operators like "| < >"

Sometimes you want to run a more complicated command that pipes its data to another command or to a file. Something like

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

or

ShellExecute('dir > output.txt');

Executing this with TProcess will not work. i.e:

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

Why using special operators to redirect output doesn't work

TProcess is just that, it's not a shell environment, only a process. It's not two processes, it's only one. It is possible to redirect output however just the way you wanted. See the next section.

How to redirect output with TProcess

You can redirect the output of a command to another command by using a TProcess instance for each command.

Here's an example that explains how to redirect the output of one process to another. To redirect the output of a process to a file/stream see the example Reading Large Output

Not only can you redirect the "normal" output (also known as stdout), but you can also redirect the error output (stderr), if you specify the poStderrToOutPut option, as seen in the options for the second process.

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.

That's it. Now you can redirect output from one program to another.

Notes

This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:

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

But our example is more crossplatform since it needs no modification to run on Windows or Linux etc. "sh" may or may not exist on your platform and is generally only available on *nix platforms. Also we have more flexibility in our example since you can read and write from/to the input, output and stderr of each process individually, which could be very advantageous for your project.

Redirecting input and output and running under root

A common problem on Unixes (FreeBSD, macOS) and Linux is that you want to execute some program under the root account (or, more generally, another user account). An example would be running the ping command.

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 FreeBSD/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. Note: this is an example only, and does not cater for large output.

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/FreeBSD/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.

Parameters which contain spaces (Replacing Shell Quotes)

In the Linux shell it is possible to write quoted arguments like this:

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

And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):

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

The best solution to avoid complicated quoting is to to switch to TProcess.Parameters.Add instead of setting the Commandline for non trivial cases e.g.

 AProcess.Executable:='/usr/bin/gdb';
 AProcess.Parameters.Add('--batch');
 AProcess.Parameters.Add('--eval-command=info symbol 0x0000DDDD'); // note the absence of quoting here
 AProcess.Parameters.Add('/home/me/myprogram');

And also remember to only pass full paths.

TProcess.Commandline however does support some basic quoting for parameters with quoting. Quote the whole parameter containing spaces with double quotes. Like this:

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


The .CommandLine property is deprecated and bugreports to support more complicated quoting cases won't accepted.

LCLIntf Alternatives

Sometimes, you don't need to explicitly call an external program to get the functionality you need. Instead of opening an application and specifying the document to go with it, just ask the OS to open the document and let it use the default application associated with that file type. Below are some examples.

Open document in default application

In some situations you need to open some document/file using default associated application rather than execute a particular program. This depends on running operating system. Lazarus provides a platform independent procedure OpenDocument which will handle it for you. Your application will continue running without waiting for the document process to close.

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

Open web page in default web browser

Just pass the URL required, the leading http:// appears to be optional under certain circumstances. Also, passing a filename appears to give the same results as OpenDocument()

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

See also:

Or, you could use TProcess like this:

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;

See also