Difference between revisions of "Executing External Programs"

From Free Pascal wiki
Jump to navigationJump to search
(indentation)
Line 13: Line 13:
 
The simplest way if you don't need pipes or any form of control is to simply use
 
The simplest way if you don't need pipes or any form of control is to simply use
  
SysUtils.ExecuteProcess( UTF8ToSys( '/full/path/to/binary'), '''' '', []);
+
<delphi>SysUtils.ExecuteProcess( UTF8ToSys( '/full/path/to/binary'), '''' '', []);</delphi>
  
 
Unfortunately the calling process 'hangs', waits until the called program
 
Unfortunately the calling process 'hangs', waits until the called program
Line 31: Line 31:
  
 
=== A Simple Example ===
 
=== A Simple Example ===
<pascal>
+
<delphi>// This is a demo program that shows how to launch
// This is a demo program that shows how to launch
+
// an external program.
// an external program.
+
program launchprogram;
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"
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 FreePascal compiler
  AProcess.CommandLine := 'ppc386 -h';
+
  AProcess.CommandLine := 'ppc386 -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 that AProcess knows what the commandline is  
  // we will run it.
+
  // 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.</delphi>
</pascal>
 
  
 
That's it! You have just learned how 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.
Line 78: Line 76:
 
Well, let's expand our example a little and do just that:
 
Well, let's expand our example a little and do just that:
  
<delphi>
+
<delphi>// This is a demo program that shows how to launch
// This is a demo program that shows how to launch
+
// an external program and read from it's output.
// an external program and read from it's output.
+
program launchprogram;
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
+
  // Create the TStringList object.
begin
+
  AStringList := TStringList.Create;
  // Now we will create the TProcess object, and
 
  // assign it to the var AProcess.
 
  AProcess := TProcess.Create(nil);
 
 
   
 
   
  // Create the TStringList object.
+
  // Tell the new AProcess what the command to execute is.
  AStringList := TStringList.Create;
+
  // Let's use the FreePascal compiler
 +
  AProcess.CommandLine := 'ppc386 -h';
 
   
 
   
  // Tell the new AProcess what the command to execute is.
+
  // We will define an option for when the program
  // Let's use the FreePascal compiler
+
  // is run. This option will make sure that our program
  AProcess.CommandLine := 'ppc386 -h';
+
  // 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];
 
   
 
   
  // We will define an option for when the program
+
  // Now that AProcess knows what the commandline is  
  // is run. This option will make sure that our program
+
  // we will run it.
  // does not continue until the program we will launch
+
  AProcess.Execute;
  // has stopped running. Also now we will tell it that
+
 
  // we want to read the output of the file.
+
  // This is not reached until ppc386 stops running.
  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 the TStringList.
+
  // into the TStringList.
  AStringList.LoadFromStream(AProcess.Output);
+
  AStringList.LoadFromStream(AProcess.Output);
 
    
 
    
  // Save the output to a file.
+
  // Save the output to a file.
  AStringList.SaveToFile('output.txt');
+
  AStringList.SaveToFile('output.txt');
 
   
 
   
  // Now that the file is saved we can free the  
+
  // Now that the file is saved we can free the  
  // TStringList and the TProcess.
+
  // TStringList and the TProcess.
  AStringList.Free;
+
  AStringList.Free;
  AProcess.Free;   
+
  AProcess.Free;   
end.
+
end.</delphi>
</delphi>
 
  
 
=== Reading large output ===
 
=== Reading large output ===
Line 141: Line 137:
 
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.
 
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.
  
<delphi> program procoutlarge;
+
<delphi>program procoutlarge;
{
+
{
    Copyright (c) 2004 by Marc Weustink
+
    Copyright (c) 2004 by Marc Weustink
 +
 
 +
    This example is creeated in the hope that it will be useful,
 +
    but WITHOUT ANY WARRANTY; without even the implied warranty of
 +
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 +
}
 
   
 
   
    This example is creeated in the hope that it will be useful,
+
uses
    but WITHOUT ANY WARRANTY; without even the implied warranty of
+
  Classes, Process, SysUtils;
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
 
}
+
const
+
  READ_BYTES = 2048;
uses
 
  Classes, Process, SysUtils;
 
 
const
 
  READ_BYTES = 2048;
 
 
    
 
    
var
+
var
  S: TStringList;
+
  S: TStringList;
  M: TMemoryStream;
+
  M: TMemoryStream;
  P: TProcess;
+
  P: TProcess;
  n: LongInt;
+
  n: LongInt;
  BytesRead: LongInt;
+
  BytesRead: LongInt;
 
   
 
   
begin
+
begin
  // We cannot use poWaitOnExit here since we don't
+
  // We cannot use poWaitOnExit here since we don't
  // know the size of the output. On Linux the size of the
+
  // know the size of the output. On Linux the size of the
  // output pipe is 2 kB. If the output data is more, we  
+
  // output pipe is 2 kB. If the output data is more, we  
  // need to read the data. This isn't possible since we are  
+
  // need to read the data. This isn't possible since we are  
  // waiting. So we get a deadlock here.
+
  // waiting. So we get a deadlock here.
  //
+
  //
  // A temp Memorystream is used to buffer the output
+
  // A temp Memorystream is used to buffer the output
 
+
 
  M := TMemoryStream.Create;
+
  M := TMemoryStream.Create;
  BytesRead := 0;
+
  BytesRead := 0;
 
   
 
   
  P := TProcess.Create(nil);
+
  P := TProcess.Create(nil);
  P.CommandLine := 'ppc386 -va bogus.pp';
+
  P.CommandLine := 'ppc386 -va bogus.pp';
  P.Options := [poUsePipes];
+
  P.Options := [poUsePipes];
  WriteLn('-- executing --');
+
  WriteLn('-- executing --');
  P.Execute;
+
  P.Execute;
  while P.Running do
+
  while P.Running do
  begin           
+
  begin           
    // make sure we have room
+
    // make sure we have room
    M.SetSize(BytesRead + READ_BYTES);
+
    M.SetSize(BytesRead + READ_BYTES);
 
      
 
      
    // try reading it
+
    // try reading it
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
+
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
    if n > 0  
+
    if n > 0  
    then begin
+
    then begin
      Inc(BytesRead, n);
+
      Inc(BytesRead, n);
      Write('.')
+
      Write('.')
    end
+
    end
    else begin     
+
    else begin     
      // no data, wait 100 ms
+
      // no data, wait 100 ms
      Sleep(100);  
+
      Sleep(100);  
    end;
+
    end;
  end;
+
  end;
  // read last part
+
  // read last part
  repeat
+
  repeat
    // make sure we have room
+
    // make sure we have room
    M.SetSize(BytesRead + READ_BYTES);
+
    M.SetSize(BytesRead + READ_BYTES);
    // try reading it
+
    // try reading it
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
+
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
    if n > 0  
+
    if n > 0  
    then begin
+
    then begin
      Inc(BytesRead, n);
+
      Inc(BytesRead, n);
      Write('.');
+
      Write('.');
    end;
+
    end;
  until n <= 0;
+
  until n <= 0;
  if BytesRead > 0 then WriteLn;
+
  if BytesRead > 0 then WriteLn;
  M.SetSize(BytesRead);  
+
  M.SetSize(BytesRead);  
  WriteLn('-- executed --');
+
  WriteLn('-- executed --');
 
    
 
    
  S := TStringList.Create;
+
  S := TStringList.Create;
  S.LoadFromStream(M);
+
  S.LoadFromStream(M);
  WriteLn('-- linecount = ', S.Count, ' --');
+
  WriteLn('-- linecount = ', S.Count, ' --');
  for n := 0 to S.Count - 1 do
+
  for n := 0 to S.Count - 1 do
  begin
+
  begin
    WriteLn('| ', S[n]);
+
    WriteLn('| ', S[n]);
  end;
+
  end;
  WriteLn('-- end --');
+
  WriteLn('-- end --');
  S.Free;
+
  S.Free;
  P.Free;
+
  P.Free;
  M.Free;
+
  M.Free;
end.</delphi>
+
end.</delphi>
  
 
=== Using input and output of a TProcess ===
 
=== Using input and output of a TProcess ===
Line 233: Line 229:
  
 
Example:
 
Example:
<delphi> {...}
+
<delphi>{...}
  AProcess:TProcess.Create(nil)
+
AProcess:=TProcess.Create(nil)
  {$IFDEF WIN32}
+
{$IFDEF WIN32}
  AProcess.CommandLine := 'calc.exe'; //Windows Calc
+
AProcess.CommandLine := 'calc.exe'; //Windows Calc
  {$ENDIF}
+
{$ENDIF}
  {$IFDEF LINUX}
+
{$IFDEF LINUX}
  AProcess.CommandLine := 'kcalc'; //KDE Calc
+
AProcess.CommandLine := 'kcalc'; //KDE Calc
  {$ENDIF}
+
{$ENDIF}
  AProcess.Execute; //in alternative, you can use AProcess.Active:=True
+
AProcess.Execute; //in alternative, you can use AProcess.Active:=True
{...}</delphi>
+
{...}</delphi>
  
 
=== Example of "talking" with aspell process ===
 
=== Example of "talking" with aspell process ===
Line 258: Line 254:
 
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 it's data to another command or to a file.
 
Something like  
 
Something like  
  ShellExecute('firstcommand.exe | secondcommand.exe');
+
<delphi>ShellExecute('firstcommand.exe | secondcommand.exe');</delphi>
 
or
 
or
  ShellExecute('dir > output.txt');
+
<delphi>ShellExecute('dir > output.txt');</delphi>
  
 
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';  
+
<delphi>// this won't work
  Process.Execute;
+
Process.CommandLine := 'firstcommand.exe | secondcommand.exe';  
 +
Process.Execute;</delphi>
 +
 
 
==== 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.
Line 275: Line 273:
  
 
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>
+
<delphi>program Project1;
  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];
 +
  SecondProcess.Options := [poUsePipes,poStderrToOutPut];
 
    
 
    
    FirstProcess.Options := [poUsePipes];
+
  FirstProcess.CommandLine := 'pwd';
    SecondProcess.Options := [poUsePipes,poStderrToOutPut];
+
  SecondProcess.CommandLine := 'grep '+ 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.</delphi>
    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 / -"';
+
<delphi>Process.Commandline := 'sh -c "pwd | grep / -"';</delphi>
 +
 
 
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.
  

Revision as of 10:57, 20 January 2011

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)

Introduction

There are multiple ways to run an external program, but this article will only focus on one: TProcess.

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

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!

SysUtils.ExecuteProcess

The simplest way if you don't need pipes or any form of control is to simply use

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

Unfortunately the calling process 'hangs', waits until the called program has terminated. So it's better to use CreateProcess() as described here:

http://msdn.microsoft.com/en-us/library/ms682512%28v=vs.85%29.aspx

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.

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

A Simple Example

<delphi>// 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 is defining 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 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.                vvvvvvvvvvvvvv
 AProcess.Options := AProcess.Options + [poWaitOnExit];

 // Now that AProcess knows what the commandline is 
 // we will run it.
 AProcess.Execute;

 // This is not reached until ppc386 stops running.
 AProcess.Free;   

end.</delphi>

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

An Improved Example

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:

<delphi>// This is a demo program that shows how to launch // an external program and read from it's 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);

 // Create the TStringList object.
 AStringList := TStringList.Create;

 // Tell the new AProcess what the command to execute is.
 // 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
 // into the TStringList.
 AStringList.LoadFromStream(AProcess.Output);
  
 // Save the output to a file.
 AStringList.SaveToFile('output.txt');

 // Now that the file is saved we can free the 
 // TStringList and the TProcess.
 AStringList.Free;
 AProcess.Free;   

end.</delphi>

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.

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.

<delphi>program procoutlarge; {

   Copyright (c) 2004 by Marc Weustink
   This example is creeated in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

}

uses

 Classes, Process, SysUtils;

const

 READ_BYTES = 2048;
  

var

 S: TStringList;
 M: TMemoryStream;
 P: TProcess;
 n: LongInt;
 BytesRead: LongInt;

begin

 // We cannot use poWaitOnExit here since we don't
 // know the size of the output. On Linux the size of the
 // output pipe is 2 kB. If the output data is more, we 
 // need to read the data. This isn't possible since we are 
 // waiting. So we get a deadlock here.
 //
 // A temp Memorystream is used to buffer the output
 
 M := TMemoryStream.Create;
 BytesRead := 0;

 P := TProcess.Create(nil);
 P.CommandLine := 'ppc386 -va bogus.pp';
 P.Options := [poUsePipes];
 WriteLn('-- executing --');
 P.Execute;
 while P.Running do
 begin          
   // make sure we have room
   M.SetSize(BytesRead + READ_BYTES);
    
   // try reading it
   n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
   if n > 0 
   then begin
     Inc(BytesRead, n);
     Write('.')
   end
   else begin     
     // no data, wait 100 ms
     Sleep(100); 
   end;
 end;
 // read last part
 repeat
   // make sure we have room
   M.SetSize(BytesRead + READ_BYTES);
   // try reading it
   n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
   if n > 0 
   then begin
     Inc(BytesRead, n);
     Write('.');
   end;
 until n <= 0;
 if BytesRead > 0 then WriteLn;
 M.SetSize(BytesRead); 
 WriteLn('-- executed --');
  
 S := TStringList.Create;
 S.LoadFromStream(M);
 WriteLn('-- linecount = ', S.Count, ' --');
 for n := 0 to S.Count - 1 do
 begin
   WriteLn('| ', S[n]);
 end;
 WriteLn('-- end --');
 S.Free;
 P.Free;
 M.Free;

end.</delphi>

Using input and output of a TProcess

See processdemo example in the Lazarus-CCR SVN.

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

Example: <delphi>{...} AProcess:=TProcess.Create(nil) {$IFDEF WIN32} AProcess.CommandLine := 'calc.exe'; //Windows Calc {$ENDIF} {$IFDEF LINUX} AProcess.CommandLine := 'kcalc'; //KDE Calc {$ENDIF} AProcess.Execute; //in alternative, you can use AProcess.Active:=True {...}</delphi>

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 it's data to another command or to a file. Something like <delphi>ShellExecute('firstcommand.exe | secondcommand.exe');</delphi> or <delphi>ShellExecute('dir > output.txt');</delphi>

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

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

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 <delphi>program Project1;

uses

 Classes, sysutils, process;
 

var

 FirstProcess,
 SecondProcess: TProcess;
 Buffer: array[0..127] of char;
 ReadCount: Integer;
 ReadSize: Integer;

begin

 FirstProcess  := TProcess.Create(nil);
 SecondProcess := TProcess.Create(nil);

 FirstProcess.Options  := [poUsePipes];
 SecondProcess.Options := [poUsePipes,poStderrToOutPut];
 
 FirstProcess.CommandLine  := 'pwd';
 SecondProcess.CommandLine := 'grep '+ DirectorySeparator+ ' -';    
 // 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.</delphi>

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: <delphi>Process.Commandline := 'sh -c "pwd | grep / -"';</delphi>

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.

See also