Difference between revisions of "Executing External Programs"
Line 154: | Line 154: | ||
Begin | Begin | ||
+ | sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password on your linux machine | ||
+ | |||
+ | |||
// The following example will open fdisk in the background and give us our partion information | // The following example will open fdisk in the background and give us our partion information | ||
// Since fdisk requires elevated privledges we need to pass our password as a parameter to sudo using -S | // Since fdisk requires elevated privledges we need to pass our password as a parameter to sudo using -S | ||
Line 161: | Line 164: | ||
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); | //hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name'); | ||
− | + | ||
− | |||
hProcess := TProcess.Create(nil); | hProcess := TProcess.Create(nil); | ||
hProcess.Executable := '/bin/sh'; | hProcess.Executable := '/bin/sh'; |
Revision as of 19:03, 25 January 2013
│
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) │
SysUtils.ExecuteProcess
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'), '', []);
For simplicity's sake, the calling process runs synchronously: it 'hangs' until the external program has finished. For more complex forms, see the next section about TProcess.
Windows CreateProcess, ShellExecute and WinExec
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). If in Delphi you used ShellExecute for documents like word documents or URLs, have a look at the open* (openurl etc) functions in lclintf.
If you need to execute external program with administrator/elevated privileges, you can use the runas method with the ShellExecute 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.
Note that the 1.0.x 'Unix.Shell has been deprecated for a while, and is removed in trunk. Use fpsystem.
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 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, you don't need to specify the full path (tested with calling cmd.exe)
A Simple Example
This example 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 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.
Since Commandline is Depreciated use the following:
This method will use Tprocess.executable with parameters.I can not confirm compatibility with any os other then linux
Since I do not use Windows, OSX etc. However Im sure its easily ported for other OS. The following code will assume
your using a Program enviroment and not a console as the code above this one.
Uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
Process;
Type
{ TForm1 }
TForm1 = Class(TForm)
Button1: TButton;
Memo1: TMemo;
Memo2: TMemo;
Procedure Button1Click(Sender: TObject);
Private
{ private declarations }
Public
{ public declarations }
End;
Var
Form1: TForm1;
Implementation
{$R *.lfm}
{ TForm1 }
Procedure TForm1.Button1Click(Sender: TObject);
var
hprocess: TProcess;
sPass: String;
Begin
sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password on your linux machine
// The following example will open fdisk in the background and give us our partion information
// Since fdisk requires elevated privledges we need to pass our password as a parameter to sudo using -S
// Option will wait till our program sends our password to the sudo application
// This method can also be used for installing applications from your repo
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name');
hProcess := TProcess.Create(nil);
hProcess.Executable := '/bin/sh';
hprocess.Parameters.Add('-c');
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l'); // Program with parameters ********
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes]; // Just what it says
hProcess.Execute; // Start our executable in this case fdisk
memo1.Lines.LoadFromStream(hprocess.Output); // Read output from executated program
memo2.Lines.LoadFromStream(hProcess.Stderr); // Read Errors Should They Occur
// Below is not needed but put here as an example how to scroll to the end of memo1
// when we receive the output otherwise you need to manually scroll, quite annoying :)
if not (hProcess.Active = true) or (hProcess.Running = true) then
begin
Memo1.SelStart := Length(Memo1.Text);
end;
hProcess.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 affect.
//hprocess.Parameters.Add('glxinfo | grep direct');
On Lazarus forums
end;
Feel free to contact me with corrections, sugguestion or just general ramification on Lazarus forums wjackon153
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);
// 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.
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, this is the code you should adapt for production use.
program procoutlarge;
{
Copyright (c) 2004-2011 by Marc Weustink and contributors
This example is created in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
}
// This is a
// WORKING
// demo program that shows
// how to launch an external program
// and read from its output.
uses
Classes, Process, SysUtils;
const
READ_BYTES = 2048;
var
OurCommand: String;
OutputLines: TStringList;
MemStream: TMemoryStream;
OurProcess: TProcess;
NumBytes: LongInt;
BytesRead: LongInt;
begin
// A temp Memorystream is used to buffer the output
MemStream := TMemoryStream.Create;
BytesRead := 0;
OurProcess := TProcess.Create(nil);
// Recursive dir is a good example.
OurCommand:='invalid command, please fix the IFDEFS.';
{$IFDEF Windows}
//Can't use dir directly, it's built in
//so we just use the shell:
OurCommand:='cmd.exe /c "dir /s c:\windows\"';
{$ENDIF Windows}
{$IFDEF Unix}
//Needs to be tested on Linux/Unix:
OurCommand := 'ls --recursive --all -l /';
{$ENDIF Unix}
writeln('-- Going to run: ' + OurCommand);
OurProcess.CommandLine := OurCommand;
// 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 if we use poWaitOnExit.
OurProcess.Options := [poUsePipes];
WriteLn('-- External program run started');
OurProcess.Execute;
while OurProcess.Running do
begin
// make sure we have room
MemStream.SetSize(BytesRead + READ_BYTES);
// try reading it
NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
if NumBytes > 0
then begin
Inc(BytesRead, NumBytes);
Write('.') //Output progress to screen.
end
else begin
// no data, wait 100 ms
Sleep(100);
end;
end;
// read last part
repeat
// make sure we have room
MemStream.SetSize(BytesRead + READ_BYTES);
// try reading it
NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
if NumBytes > 0
then begin
Inc(BytesRead, NumBytes);
Write('.');
end;
until NumBytes <= 0;
if BytesRead > 0 then WriteLn;
MemStream.SetSize(BytesRead);
WriteLn('-- External program run complete');
OutputLines := TStringList.Create;
OutputLines.LoadFromStream(MemStream);
WriteLn('-- External program output line count = ', OutputLines.Count, ' --');
for NumBytes := 0 to OutputLines.Count - 1 do
begin
WriteLn(OutputLines[NumBytes]);
end;
WriteLn('-- Program end');
OutputLines.Free;
OurProcess.Free;
MemStream.Free;
end.
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:
{...}
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
{...}
OS X 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('/Application/iCal.app');
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');
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];
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.
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 (OSX) 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 Linux/OSX, and supplying input on stdin}
{$mode objfpc}{$H+}
uses
Classes,
Math, {for min}
Process;
procedure RunsLsRoot;
var
Proc: TProcess;
CharBuffer: array [0..511] of char;
ReadCount: integer;
ExitCode: integer;
SudoPassword: string;
begin
WriteLn('Please enter the sudo password:');
Readln(SudoPassword);
ExitCode := -1; //Start out with failure, let's see later if it works
Proc := TProcess.Create(nil); //Create a new process
try
Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr
Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo
// -S causes sudo to read the password from stdin.
Proc.Execute; //start it. sudo will now probably ask for a password
// write the password to stdin of the sudo program:
SudoPassword := SudoPassword + LineEnding;
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
SudoPassword := '%*'; //short string, hope this will scramble memory a bit; note: using PChars is more fool-proof
SudoPassword := ''; // and make the program a bit safer from snooping?!?
// main loop to read output from stdout and stderr of sudo
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
(Proc.Stderr.NumBytesAvailable > 0) do
begin
// read stdout and write to our stdout
while Proc.Output.NumBytesAvailable > 0 do
begin
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more
Proc.Output.Read(CharBuffer, ReadCount);
Write(StdOut, Copy(CharBuffer, 0, ReadCount));
end;
// read stderr and write to our stderr
while Proc.Stderr.NumBytesAvailable > 0 do
begin
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more
Proc.Stderr.Read(CharBuffer, ReadCount);
Write(StdErr, Copy(CharBuffer, 0, ReadCount));
end;
end;
ExitCode := Proc.ExitStatus;
finally
Proc.Free;
Halt(ExitCode);
end;
end;
begin
RunsLsRoot;
end.
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.
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
TProcess and also pass parameters containing spaces, but it uses a different quoting style. Instead of only quoting part of the parameter, quote all of it. Like this:
TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';
And also remember to only pass full paths.
See also this discussion about it: http://bugs.freepascal.org/view.php?id=14446
Alternatives
Sometimes, you don't need to explicitly call an external program to get the functionality you need. Below are some examples.
Open document in default application
In some situations you need to open some document/file using default associated application rather then execute particular program. This depends on running operating system and Lazarus provides platform independent procedure OpenDocument which will handle it for you.
uses LCLIntf;
...
OpenDocument('manual.pdf');
...
Process executed according OS:
- Windows: open
- Linux general: xdg-open
- Linux GNOME: gnome-open
- Linux KDE: kfmclient
- OS X: open
Open web page in default web browser
Something like this would be easiest:
uses LCLIntf;
...
OpenURL(URL);
However, you could use something like this:
uses Process, LCLIntf;
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;