Difference between revisions of "Executing External Programs/ru"

From Free Pascal wiki
Jump to navigationJump to search
Line 541: Line 541:
 
Вот и все. Теперь вы можете перенаправлять вывод из одной программы в другую.
 
Вот и все. Теперь вы можете перенаправлять вывод из одной программы в другую.
  
==== Notes ====
+
==== Заметки ====
This example may seem overdone since it's possible to run "complicated" commands using a shell with TProcess like:
+
Этот пример может показаться излишним, поскольку можно запускать «сложные» команды, используя оболочку с TProcess, например:
 
<syntaxhighlight lang=pascal>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight>
 
<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.
+
Но наш пример более кроссплатформенный, так как он не нуждается в модификации для работы в Windows или Linux и т.д. "sh" может существовать или не существовать на вашей платформе и обычно доступен только на платформах *nix. Кроме того, у нас больше гибкости в нашем примере, так как вы можете читать и записывать из/в ввода, вывода и stderr каждого процесса в отдельности, что может быть очень выгодно для вашего проекта.
  
 
===Redirecting input and output and running under root===
 
===Redirecting input and output and running under root===

Revision as of 23:51, 4 August 2019

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

Введение: сравнение

В библиотеках RTL, FCL, LCL есть разные способы выполнить внешнюю программу.

Метод Библиотека Платформы Одной строкой? Особенности
ExecuteProcess RTL кроссплатформенный Да Очень ограничен, синхронен
ShellExecute WinAPI Только MS Windows Да Много. Может запускать программы с правами администратора
fpsystem, fpexecve Unix Unix only
TProcess FCL кроссплатформенный Нет Все
RunCommand FCL кроссплатформенный Требуется FPC 2.6.2+ Да Покрывает стандартное использование TProcess
OpenDocument LCL кроссплатформенный Да Только открывает документ. Документ будет открыт стандартной программой

Если вы использовали ShellExecute и/или WinExec в Delphi, то вы можете начать использовать TProcess как альтернативу в FPC/Lazarus (это верно и в случае использования Lazarus на Linux, потому что TProcess является кроссплатформенным компонентом).

Light bulb  Примечание: FPC/Lazarus поддерживает ShellExecute и WinExec, но только в среде Win32. Если вы пишете кросс-платформенную программу, то лучшим путем будет использование TProcess!

(Process.)RunCommand

В FPC 2.6.2, некоторые вспомогательные функции для TProcess были добавлены в модуль process основанный на обертке использованной в fpcup. Эти функции могут быть для базового и среднего уровня использования и могот захватить вывод как одной строки, так и огромного количества выводимой информации.

Простой пример:

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

Перегруженный вариант функции может возвращать код выхода программы. RunCommandInDir запускает программу в заданной папке.

function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;
function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;
function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;

SysUtils.ExecuteProcess

Простейший путь, если вам не нужно общение с процессом - это просто использовать вызов

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

При использовании этого метода, приложение "повисает" до тех пор, пока вызванная программа не завершится. Это может быть полезно если пользователь должен сделать что-то перед использованием вашего приложения. Если вы хотите обойти это ограничение, то более полезный кроссплатформенный способ - это TProcess, или если ваша целевая платформа только Windows, то используйте ShellExecute.

MS Windows : CreateProcess, ShellExecute и WinExec

Light bulb  Примечание: FPC/Lazarus поддерживают CreateProcess, ShellExecute и/или WinExec, только на Win32/64. Если ваша программа кроссплатформенная, используйте RunCommand или TProcess.
Light bulb  Примечание: WinExec использует 16-битные вызовы и давно устарел. Новые версии FPC при его использовании генерируют предупреждение.

ShellExecute - стандартная функция MS Windows (ShellApi.h) с хорошей документацией на MSDN (читайте документацию, если вам кажется, что функция ненадежна).

uses ..., ShellApi;

// Обычный текст (возвращаемые ошибки игнорируются) :
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) = 0 then;

// Выполняем Batch файл:
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) = 0 then;

// Открываем командную строку в заданной папке:
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) = 0 then;

// Открываем веб-страницу стандартным браузером с помощью'start' (через скрытую командную строку) :
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;

// или полезную команду:
procedure RunShellExecute(const prog,params:string);
begin
  //  ( Handle, nil/'open'/'edit'/'find'/'explore'/'print',   // 'open' не всегда требуется 
  //      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; //успех
  // если возвращается число в диапазоне 0..32, то значит ошибка
end;

Также есть WideChar функции - ShellExecuteExW , ShellExecuteExA в режиме AnsiChar.

В качестве fMask можно ипользовать SEE_MASK_DOENVSUBST, SEE_MASK_FLAG_NO_UI или SEE_MASK_NOCLOSEPROCESS и тд Если в Delphi вы используете ShellExecute для открытия документов, например документ Word или ссылки, то присмотритесь к open* (openurl и т.д.) модуле lclintf.

Использование ShellExecuteEx с повышенными (правами администратора) правами

Если вы хотите использовать программу с повышенными (правами администратора) правами, используйте runas как альтернативу ShellExecuteEx:

uses ShellApi, ...;

function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;
var
  sei: TShellExecuteInfoA;
begin
  FillChar(sei, SizeOf(sei), 0);
  sei.cbSize := SizeOf(sei);
  sei.Wnd := Handle;
  sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
  sei.lpVerb := 'runas';
  sei.lpFile := PAnsiChar(Path);
  sei.lpParameters := PAnsiChar(Params);
  sei.nShow := SW_SHOWNORMAL;
  Result := ShellExecuteExA(@sei);
end;

procedure TFormMain.RunAddOrRemoveApplication;
begin
  // Пример, открывающий панель управления с повышенными правами
  RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');
end;

Unix fpsystem, fpexecve и shell

Эти функции являются платформозависимыми.

Учтите, что Unix.Shell версии 1.0.x устарел и удален из trunk. Используйте fpsystem.

TProcess

Вы можете использовать TProcess для запуска внешних программ. Самыми полезными вещами при этом будут:

  • Платформонезависимость
  • Способность читать из stdout и писать в stdin.
Light bulb  Примечание: TProcess не оболочка! И не терминал! Вы не можете напрямую исполнять скрипты или перенаправлять вывод используя такие операторы, как "

Важно: Вы должны определять полный путь к исполняемому файлу. Например '/bin/cp' вместо 'cp'. Если программа находится где-либо в переменной PATH, то вы можете использовать функцию FindDefaultExecutablePath из модуля LCL FileUtil.

Простейший пример

Многие типичные случаи были подготовлены в функциях Runcommand. Прежде, чем начать копировать и вставлять приведенные ниже примеры, сначала проверьте их.

Простой пример

 // Демо-программа, показывающая, как можно запустить
 // внешнюю программу
 program launchprogram;
 
 // Подключаем модули с требуемыми
 // нам процедурами и функциями.
 uses 
   Classes, SysUtils, Process;
 
 // Опишем переменную "AProcess" 
 // типа "TProcess"
 var 
   AProcess: TProcess;
 
 // Здесь наша программа начинается
 begin
   // Создаем объект  TProcess и
   // присваиваем его переменной AProcess.
   AProcess := TProcess.Create(nil);
 
   // Сообщим AProcess сомандную строку для запуска
   // Let's use the FreePascal compiler
   AProcess.CommandLine := 'ppc386 -h';
 
   // Необходимо описать опции программы для запуска
   // Эта опция не позволит нашей программе выполнятся до тех пор, пока 
   // запущенная программа не закончится
   AProcess.Options := AProcess.Options + [poWaitOnExit];
 
   // Теперь AProcess знает командную строку
   // и мы ее запускаем
   AProcess.Execute;
 
   // Пока ppc386 не прекратит работу, мы досюда не дойдем
   AProcess.Free;   
 end.

Вот оно! Теперь вы научились запускать внешнюю программу изнутри вашей собственной.

Усовершенствованный пример

Это все замечательно, но как я могу получить вывод программы, которую я запустил?

Хорошо, пусть наш пример немного увеличится и теперь будет выглядеть так:

 // Это демо-программа, показывающая, как запускать
 // внешнюю программу и читать ее вывод
 program launchprogram;
 
 // Подключаем модули
 uses 
   Classes, SysUtils, Process;
 
 // Опысываем переменную "AProcess"
 // И добавляем список строк TStringList для сбора данных
 // из вывода программы
 var 
   AProcess: TProcess;
   AStringList: TStringList;
 
 // Начинаем нашу программу
 begin
   // Создаем объект TProcess 
   AProcess := TProcess.Create(nil);
 
   // Создаем объект TStringList
   AStringList := TStringList.Create;
 
   // Зададим командную строку
   AProcess.CommandLine := 'ppc386 -h';
 
   // Установим опции программы. Первая из них не позволит нашей программе
   // выполнятся до тех пор, пока не закончит выполнение запущенная программа
   // Также добавим опцию, которая говорит, что мы хотим прочитать 
   // вывод запущенной программы
   AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 
   // Теперь запускаем программу
   AProcess.Execute;
   
   // Пока запущенная программа не закончится, досюда мы не дойдем
 
   // А теперь прочитаем вывод в список строк TStringList.
   AStringList.LoadFromStream(AProcess.Output);
   
   // Сохраним вывод в файл
   AStringList.SaveToFile('output.txt');
 
   // После сохранения файла мы можем уничтожить
   // TStringList и TProcess.
   AStringList.Free;
   AProcess.Free;   
 end.

Чтение больших объемов вывода

В предыдущем примере мы ждали завершения запущенной программы. После этого мы считывали все, что программа записала в выходной поток. Но ведь может оказаться и так, что программа выведет много данных, канал заполнится и вывод остановится, при этом запустившая программа ждет завершения запущенной программы, которая в свою очередь не может завершить работу, пока не выведет все данные. Возникает коллизия, dead-lock.

Поэтому следующий пример не будет использовать опцию poWaitOnExit и будет читать данные при запущенной программе. Вывод записывается в поток, который можно позже использовать для чтения его содержимого в TStringList.

 program procoutlarge;
 {
     Copyright (c) 2004 by Marc Weustink
 
     Этот пример был создан в надежде быть вам полезным,
     но без каких-либо подразумеваемых или явных  гарантий; 
 }
 
 uses
   Classes, Process, SysUtils;
 
 const
   READ_BYTES = 2048;
   
 var
   S: TStringList;
   M: TMemoryStream;
   P: TProcess;
   n: LongInt;
   BytesRead: LongInt;
 
 begin
   // Мы не можем использовать poWaitOnExit не зная размер вывода
   // В Linux размер выходного канала равен 2 kB. Если размер выводных
   // данных больше, то мы должны считывать данные. 
   // Пока мы ждем, чтение невозможно. Соответственно, мы получаем 
   // коллизию.
   //
   // Используем для буфера временный  поток Memorystream 
   
   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          
     // Убедимся, что нам хватит места
     M.SetSize(BytesRead + READ_BYTES);
     
     // попытаемся прочитать данные
     n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
     if n > 0 
     then begin
       Inc(BytesRead, n);
       Write('.')
     end
     else begin     
       // нет данных, ждем 100 ms
       Sleep(100); 
     end;
   end;
   // читаем последний блок
   repeat
     // убедимся, что хватает места
     M.SetSize(BytesRead + READ_BYTES);
     // пытаемся прочитать
     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.

Использование ввода и вывода TProcess

Смотри демо пример на Lazarus-CCR SVN.

Некоторые подсказки при использовании TProcess

Если вы создаете кроссплатформенную программу, вы можете изменять командную строку применительно к каждой ОС использую директивы "{$IFDEF}" и "{$ENDIF}".

Например:

 {...}
   AProcess:TProcess.Create(nil)
   {$IFDEF WIN32}
   AProcess.CommandLine := 'calc.exe'; // Windows калькулятор
   {$ENDIF}
   {$IFDEF LINUX}
   AProcess.CommandLine := 'kcalc'; // KDE калькулятор
   {$ENDIF}
   AProcess.Execute; // как альтернативу, вы можете использовать AProcess.Active:=True
 {...}

Показ комплекта приложений на переднем плане в OS X

Вы можете запустить application bundle(пакет приложения) через TProcess, вызвав исполняемый файл внутри пакета. Например:

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

Это запустит Calendar, но окно будет позади текущего приложения. Чтобы получить приложение на переднем плане, вы можете использовать утилиту open с параметром -n:

 AProcess.Executable:='/usr/bin/open';
 AProcess.Parameters.Add('-n');
 AProcess.Parameters.Add('-a'); //необязательно: скрыть терминал - аналогично опции Windows poNoConsole
 AProcess.Parameters.Add('/Application/iCal.app');

Если вашему приложению нужны параметры, вы можете передать open параметр --args, после чего все параметры будут переданы приложению:

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

Запуск отдельной программы

Обычно программа, запускаемая вашим приложением, является дочерним процессом и уничтожается, когда ваше приложение уничтожается. Если вы хотите запустить автономную программу, которая продолжает работать [после завершения вашего приложения], вы можете использовать следующее:

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

    // Копируем переменные среды по умолчанию, включая переменную DISPLAY, для работы приложения с графическим интерфейсом
    for I := 1 to GetEnvironmentVariableCount do
      Process.Environment.Add(GetEnvironmentString(I));

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

Пример "общения" с процессом aspell

Внутри исходного кода pasdoc вы можете найти два модуля, которые выполняют проверку орфографии, "общаясь" с запущенным процессом aspell через каналы:

  • Модуль PasDoc_ProcessLineTalk.pas реализует класс TProcessLineTalk, потомок TProcess, который может быть легко использован для общения с любым процессом построчно.
  • Модуль PasDoc_Aspell.pas реализует класс TAspellProcess, который выполняет проверку орфографии с использованием базового экземпляра TProcessLineTalk для выполнения aspell и связи с запущенным процессом aspell.

Оба модуля довольно независимы от остальных исходников pasdoc, поэтому они могут служить реальными примерами использования TProcess для запуска и связи по каналам с другой программой.

Замена операторов командной оболочки, таких как "| < >"

Иногда у вас есть желание выполнить более сложную команду, которая передает свои данные другой команде или файлу. Что-то вроде

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

или

ShellExecute('dir > output.txt');

Запуск таких команд с TProcess не будет работать. То есть:

// так работать не будет
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; 
Process.Execute;

Почему использование специальных операторов для перенаправления вывода не работает

TProcess - это не оболочка, а процесс. Это не два процесса, а всего один. Можно перенаправить вывод так, как вы этого пожелаете. См. следующий раздел.

Как перенаправить вывод с помощью TProcess

Вы можете перенаправлять вывод команды другой команде, используя экземпляр TProcess для каждой команды.

Вот пример, который объясняет, как перенаправить вывод одного процесса другому. Чтобы перенаправить вывод процесса в файл/поток, см. пример чтение больших объемов вывода.

Вы можете не только перенаправлять «обычный» вывод (также известный как stdout), но также можете перенаправить вывод ошибок (stderr), если вы укажете опцию poStderrToOutPut, как видно в настройках для второго процесса.

program Project1;
  
uses
  Classes, sysutils, process;
  
var
  FirstProcess,
  SecondProcess: TProcess;
  Buffer: array[0..127] of char;
  ReadCount: Integer;
  ReadSize: Integer;
begin
  FirstProcess  := TProcess.Create(nil);
  SecondProcess := TProcess.Create(nil);
 
  FirstProcess.Options     := [poUsePipes]; 
  FirstProcess.Executable  := 'pwd'; 
  
  SecondProcess.Options    := [poUsePipes,poStderrToOutPut];
  SecondProcess.Executable := 'grep'; 
  SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); 
  // это было бы аналогично "pwd | grep / -"
  
  FirstProcess.Execute;
  SecondProcess.Execute;
  
  while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
  begin
    if FirstProcess.Output.NumBytesAvailable > 0 then
    begin
      // убеждаемся, что мы не читаем больше данных, чем мы выделили
      // для этого места в буфере
      ReadSize := FirstProcess.Output.NumBytesAvailable;
      if ReadSize > SizeOf(Buffer) then
        ReadSize := SizeOf(Buffer);
      // теперь считываем вывод в буфер
      ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
      // и пишем буфер во второй процесс
      SecondProcess.Input.Write(Buffer[0], ReadCount);
  
      // если SecondProcess записывает много данных в свой Output, то 
      // мы должны прочитать эти данные здесь, чтобы предотвратить блокировку
      // см. предыдущий пример «Чтение больших объемов вывода»
    end;
  end;
  // Закрываем вход на SecondProcess,
  // поэтому он заканчивает обработку своих данных
  SecondProcess.CloseInput;
 
  // и дожидаемся его завершения 
  //  будьте осторожны с тем, какую команду вы запускаете, потому что она может не завершиться, 
  // когда ее вход закроется, следующая строка может зациклиться навечно
  while SecondProcess.Running do
    Sleep(1);
  // вот и все! остальная часть программы просто в качестве примера
  // это немного «полезно»

  // мы будем повторно использовать буфер для вывода SecondProcess'а
  // вывод в *эту* программу 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:');
  
  // освобождаем объекты нашего процесса
  FirstProcess.Free;
  SecondProcess.Free;
end.

Вот и все. Теперь вы можете перенаправлять вывод из одной программы в другую.

Заметки

Этот пример может показаться излишним, поскольку можно запускать «сложные» команды, используя оболочку с TProcess, например:

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

Но наш пример более кроссплатформенный, так как он не нуждается в модификации для работы в Windows или Linux и т.д. "sh" может существовать или не существовать на вашей платформе и обычно доступен только на платформах *nix. Кроме того, у нас больше гибкости в нашем примере, так как вы можете читать и записывать из/в ввода, вывода и stderr каждого процесса в отдельности, что может быть очень выгодно для вашего проекта.

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.

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/OSX, we need specify full path to our executable:
  hProcess.Executable := '/bin/sh';
  // Now we add all the parameters on the command line:
  hprocess.Parameters.Add('-c');
  // Here we pipe the password to the sudo command which then executes fdisk -l: 
  hprocess.Parameters.add('echo ' + sPass  + ' | sudo -S fdisk -l');
  // Run asynchronously (wait for process to exit) and use pipes so we can read the output pipe
  hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
  // Now run:
  hProcess.Execute;

  // hProcess should have now run the external executable (because we use poWaitOnExit).
  // Now you can process the process output (standard output and standard error), eg:
  OutputLines.Add('stdout:');
  OutputLines.LoadFromStream(hprocess.Output);
  OutputLines.Add('stderr:');
  OutputLines.LoadFromStream(hProcess.Stderr);
  // Show output on screen:
  writeln(OutputLines.Text);

  // Clean up to avoid memory leaks:
  hProcess.Free;
  OutputLines.Free;
  
  //Below are some examples as you see we can pass illegal characters just as if done from terminal 
  //Even though you have read elsewhere that you can not I assure with this method you can :)

  //hprocess.Parameters.Add('ping -c 1 www.google.com');
  //hprocess.Parameters.Add('ifconfig wlan0 | grep ' +  QuotedStr('inet addr:') + ' | cut -d: -f2');

  //Using QuotedStr() is not a requirement though it makes for cleaner code;
  //you can use double quote and have the same effect.

  //hprocess.Parameters.Add('glxinfo | grep direct');   

  // This method can also be used for installing applications from your repository:

  //hprocess.Parameters.add('echo ' + sPass  + ' | sudo -S apt-get install -y pkg-name'); 

 end.

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

TProcess can 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

Альтернативные решения с помощью LCLIntf

Иногда вам нет необходимости явно вызывать внешнюю программу для получения необходимой функциональности. Вместо запуска приложения и передачи в него документа, просто выполните запрос через операционную систему открыть документ и использовать для этого приложение по-умолчанию, ассоциированное с данным типом файлов. Ниже представлены некоторые примеры.

Открыть документ в приложении по умолчанию

В некоторых ситуациях вам необходимо открыть документ/файл в приложении, используемом по умолчанию для данного типа документов. Это зависит от того, в какой операционной системе выполняется приложение. Lazarus предоставляет платформонезависимую процедуру OpenDocument, которая будет обрабатывать такой запрос. Ваше приложение будет продолжать работать, не дожидаясь закрытия документа в вызываемом приложении.

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

Открыть страницу в браузере по умолчанию

Просто передайте необходимый URL; при этом указание http:// при определенных обстоятельствах является необязательным. Передача имени файла дает такой же результат, как и использование OpenDocument()

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

См. также:

Кроме того, вы можете использовать TProcess как в данном примере:

uses Process;

procedure OpenWebPage(URL: string);
// Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"
var
  Browser, Params: string;
begin
  FindDefaultBrowser(Browser, Params);
  with TProcess.Create(nil) do
  try
    Executable := Browser;
    Params:=Format(Params, [URL]);
    Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself
    Parameters.Add(Params);
    Options := [poNoConsole];
    Execute;
  finally
    Free;
  end;
end;

См. также