Difference between revisions of "Executing External Programs/ru"

From Free Pascal wiki
Jump to navigationJump to search
m (Fix typos)
(29 intermediate revisions by 2 users not shown)
Line 119: Line 119:
  
 
В качестве  fMask можно ипользовать SEE_MASK_DOENVSUBST, SEE_MASK_FLAG_NO_UI или SEE_MASK_NOCLOSEPROCESS и тд
 
В качестве  fMask можно ипользовать SEE_MASK_DOENVSUBST, SEE_MASK_FLAG_NO_UI или SEE_MASK_NOCLOSEPROCESS и тд
Если в Delphi вы используете ShellExecute для открытия документов, например документ Word или ссылки, то присмотритесь к open* (openurl и т.д.) модуле lclintf.
+
Если в Delphi вы используете ShellExecute для открытия документов, например документ Word или ссылки, то присмотритесь к open* (OpenURL и т.д.) модуле lclintf.
  
 
=== Использование ShellExecuteEx с повышенными (правами администратора) правами===
 
=== Использование ShellExecuteEx с повышенными (правами администратора) правами===
Line 194: Line 194:
 
   AProcess := TProcess.Create(nil);
 
   AProcess := TProcess.Create(nil);
 
   
 
   
   // Сообщим AProcess сомандную строку для запуска
+
   // Сообщаем новому AProcess, что это за команда для выполнения.
  // Let's use the FreePascal compiler
+
   // Давайте использовать компилятор Free Pascal (версия i386)
   AProcess.CommandLine := 'ppc386 -h';
+
   AProcess.Executable:= 'ppc386';
 +
 
 +
  // Передаеv -h вместе с ppc386, чтобы фактически выполнить 'ppc386 -h':
 +
  AProcess.Parameters.Add('-h');
 
   
 
   
   // Необходимо описать опции программы для запуска
+
   // Мы определим опцию, когда программа
   // Эта опция не позволит нашей программе выполнятся до тех пор, пока  
+
   // будет запущена. Эта опция гарантирует, что наша программа
   // запущенная программа не закончится
+
  // не продолжит работу, пока программа, которую мы запустили,
 +
   // не перестанет работать. vvvvvvvvvvvvvv
 
   AProcess.Options := AProcess.Options + [poWaitOnExit];
 
   AProcess.Options := AProcess.Options + [poWaitOnExit];
 
   
 
   
   // Теперь AProcess знает командную строку
+
   // Теперь пусть Process запустит программу
  // и мы ее запускаем
 
 
   AProcess.Execute;
 
   AProcess.Execute;
 
   
 
   
Line 214: Line 217:
 
Вот оно! Теперь вы научились запускать внешнюю программу изнутри вашей собственной.
 
Вот оно! Теперь вы научились запускать внешнюю программу изнутри вашей собственной.
  
=== Усовершенствованный пример ===
 
Это все замечательно, но как я могу получить вывод программы, которую я запустил?
 
  
Хорошо, пусть наш пример немного увеличится и теперь будет выглядеть так:
+
----
 +
[[User:Zoltanleo|Прим.перев.]]: на момент правки статьи (fpc 3.0.4) свойство ''CommandLine'' (как и ''ApplicationName'') согласно [https://www.freepascal.org/docs-html/current/fcl/process/tprocess.html документации ] объявлены '''deprecated'''. Вместо них рекомендуется использовать свойства [https://www.freepascal.org/docs-html/current/fcl/process/tprocess.parameters.html Parameters] и [https://www.freepascal.org/docs-html/current/fcl/process/tprocess.executable.html Executable] соответственно.
 +
 
 +
{{Note| для пользователей Windows - если путь к исполняемому файлу включает символы пробела, табуляции или перевода строки, то он должен заключаться в двойные кавычки (например, "c:\Program Files\Git\bin\git.exe"). Это же правило справедливо в случае, если такой путь указывается в аргументах командной строки (в свойстве Parameters).}}
  
 +
{{Note| строковый агрумент, добавляемый в Parameters, не должен содержать пробелы.}}
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
// Это демо-программа, показывающая, как запускать
+
...
// внешнюю программу и читать ее вывод
+
// к примеру, один из аргументов командной строки должен быть '-user sysdba'
program launchprogram;
+
//тогда он должен быть записан так
 +
AProcess.Parameters.Add('-user');
 +
AProcess.Parameters.Add('sysdba');
 +
...
 +
</syntaxhighlight>
 +
----
 +
 
 +
=== Усовершенствованный пример (но пока не правильный) ===
 +
Это все замечательно, но как я могу получить вывод программы, которую я запустил?
 +
Что ж, давайте немного расширим наш пример и сделаем так: '''этот пример прост, поэтому вы можете извлечь из него уроки. Пожалуйста, не используйте этот пример в рабочем коде, а используйте код из раздела''' [[Executing_External_Programs/ru#.D0.A7.D1.82.D0.B5.D0.BD.D0.B8.D0.B5_.D0.B1.D0.BE.D0.BB.D1.8C.D1.88.D0.B8.D1.85_.D0.BE.D0.B1.D1.8A.D0.B5.D0.BC.D0.BE.D0.B2_.D0.B2.D1.8B.D0.B2.D0.BE.D0.B4.D0.B0|Чтение больших объемов вывода]]
 +
 
 +
<syntaxhighlight lang="pascal">
 +
// Это
 +
// ДЕФЕКТНАЯ
 +
// демонстрационная программа, которая показывает
 +
// как запустить внешнюю программу
 +
// и читать из ее вывода.
 +
program launchprogram;
 
   
 
   
// Подключаем модули
+
// Здесь мы включаем файлы, которые имеют полезные функции
uses  
+
// и процедуры, и которые нам понадобятся.
  Classes, SysUtils, Process;
+
uses  
 +
  Classes, SysUtils, Process;
 
   
 
   
// Опысываем переменную "AProcess"
+
// Это определение переменной «AProcess», как переменной
// И добавляем список строк TStringList для сбора данных
+
// типа "TProcess".
// из вывода программы
+
// Также еще мы добавляем TStringList для хранения
var  
+
// данных, читаемых из вывода программ.
  AProcess: TProcess;
+
var  
  AStringList: TStringList;
+
  AProcess: TProcess;
 +
  AStringList: TStringList;
 +
 
 +
// Это место, где наша программа начинает работать
 +
begin
 +
  // Теперь мы создадим объект Process и
 +
  // назначим его переменной var AProcess.
 +
  AProcess := TProcess.Create(nil);
 
   
 
   
// Начинаем нашу программу
+
  // Сообщим новому AProcess, что это за команда для выполнения.
begin
+
  AProcess.Executable := '/usr/bin/ppc386';
  // Создаем объект TProcess
+
  AProcess.Parameters.Add('-h');
  AProcess := TProcess.Create(nil);
+
 
 +
  // Мы определим опцию, когда программа
 +
  // выполняется. Эта опция гарантирует, что наш AProcess
 +
  // не будет продолжаться до тех пор, пока запущенная нами программа
 +
  // остановится. Также теперь мы сообщим AProcess, что
 +
  // мы хотим прочитать вывод файла.
 +
  AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 
   
 
   
  // Создаем объект TStringList
+
  // Теперь, когда AProcess знает, что это за командная строка, его можно запустить.
  AStringList := TStringList.Create;
+
  AProcess.Execute;
 +
 
 +
  // После завершения AProcess, остальная часть программы будет выполнена.
 
   
 
   
  // Зададим командную строку
+
  // Теперь читаем вывод программы, который мы только что запихали в TStringList.
  AProcess.CommandLine := 'ppc386 -h';
+
  AStringList := TStringList.Create;
+
  AStringList.LoadFromStream(AProcess.Output);
  // Установим опции программы. Первая из них не позволит нашей программе
 
  // выполнятся до тех пор, пока не закончит выполнение запущенная программа
 
  // Также добавим опцию, которая говорит, что мы хотим прочитать
 
  // вывод запущенной программы
 
  AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 
 
  // Теперь запускаем программу
 
  AProcess.Execute;
 
 
    
 
    
  // Пока запущенная программа не закончится, досюда мы не дойдем
+
  // Сохраняем выходные данные в файл и очищаем TStringList.
 +
  AStringList.SaveToFile('output.txt');
 +
  AStringList.Free;
 
   
 
   
  // А теперь прочитаем вывод в список строк TStringList.
+
  // Теперь, когда выходные данные процесса обработаны, его можно освободить.
  AStringList.LoadFromStream(AProcess.Output);
+
  AProcess.Free;   
 
+
end.</syntaxhighlight>
  // Сохраним вывод в файл
 
  AStringList.SaveToFile('output.txt');
 
 
  // После сохранения файла мы можем уничтожить
 
  // TStringList и TProcess.
 
  AStringList.Free;
 
  AProcess.Free;   
 
end.
 
</syntaxhighlight>
 
  
 
=== Чтение больших объемов вывода ===
 
=== Чтение больших объемов вывода ===
Line 275: Line 298:
 
Поэтому следующий пример не будет использовать опцию poWaitOnExit и будет читать данные при запущенной программе. Вывод записывается в поток, который можно позже использовать для чтения его содержимого в TStringList.
 
Поэтому следующий пример не будет использовать опцию poWaitOnExit и будет читать данные при запущенной программе. Вывод записывается в поток, который можно позже использовать для чтения его содержимого в TStringList.
  
<syntaxhighlight lang=pascal> program procoutlarge;
+
Если вы хотите считывать выходные данные из внешнего процесса, вы должны адаптировать этот код для повседневного использования.
{
+
 
    Copyright (c) 2004 by Marc Weustink
+
<syntaxhighlight lang=pascal> program LargeOutputDemo;
+
 
    Этот пример был создан в надежде быть вам полезным,
+
{$mode objfpc}{$H+}
    но без каких-либо подразумеваемых или явных  гарантий;
 
}
 
 
   
 
   
 
  uses
 
  uses
   Classes, Process, SysUtils;
+
   Classes, Process, SysUtils; // Process - модуль, содержащий TProcess
+
 
const
+
const
  READ_BYTES = 2048;
+
  BUF_SIZE = 2048; // Размер буфера для чтения выходных данных блоками
 
+
var
var
+
  AProcess    : TProcess;
  S: TStringList;
+
  OutputStream : TStream;
  M: TMemoryStream;
+
  BytesRead   : longint;
  P: TProcess;
+
  Buffer      : array[1..BUF_SIZE] of byte;
   n: LongInt;
+
 
  BytesRead: LongInt;
+
begin
+
  // Установки процесса; в качестве примера используется рекурсивный поиск в каталоге,
begin
+
  // потому что это обычно приводит к большому количеству данных.
  // Мы не можем использовать poWaitOnExit не зная размер вывода
+
  AProcess := TProcess.Create(nil);
  // В Linux размер выходного канала равен 2 kB. Если размер выводных
+
 
  // данных больше, то мы должны считывать данные.  
+
  //Команды для Windows и *nix отличаются, поэтому используется директива $IFDEFs
  // Пока мы ждем, чтение невозможно. Соответственно, мы получаем
+
  {$IFDEF Windows}
  // коллизию.
+
    // В Windows команду dir нельзя использовать напрямую, потому что она является встроенной
  //
+
    // командой оболочки. Поэтому нужны cmd.exe и дополнительные параметры
  // Используем для буфера временный  поток Memorystream
+
    AProcess.Executable := 'c:\windows\system32\cmd.exe';
 
+
    AProcess.Parameters.Add('/c');
  M := TMemoryStream.Create;
+
    AProcess.Parameters.Add('dir /s c:\windows');
  BytesRead := 0;
+
  {$ENDIF Windows}
+
 
  P := TProcess.Create(nil);
+
  {$IFDEF Unix}
  P.CommandLine := 'ppc386 -va bogus.pp';
+
    AProcess.Executable := '/bin/ls';
  P.Options := [poUsePipes];
+
    AProcess.Parameters.Add('--recursive');
  WriteLn('-- executing --');
+
    AProcess.Parameters.Add('--all');
  P.Execute;
+
    AProcess.Parameters.Add('-l');
  while P.Running do
+
  {$ENDIF Unix}
  begin         
+
 
    // Убедимся, что нам хватит места
+
  // Необходимо использовать poUsePipes - параметр TProcess, чтобы можно было получить выходные данные.
    M.SetSize(BytesRead + READ_BYTES);
+
  // Параметр TProcess'а poWaitOnExit не может быть использован, потому что он заблокирует
   
+
  // эту программу до окончания вызванного процесса, препятствуя чтению выходных данных процесса.
    // попытаемся прочитать данные
+
  AProcess.Options := [poUsePipes];
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
+
 
    if n > 0
+
  //Запуск процесса (выполнение команды dir/ls)
    then begin
+
  AProcess.Execute;
      Inc(BytesRead, n);
+
 
      Write('.')
+
  // Создаем объект потока для хранения сгенерированного вывода. Вывод может
    end
+
  // также являться файловым потоком для непосредственного сохранения выводимых данных на диск.
    else begin      
+
  OutputStream := TMemoryStream.Create;
      // нет данных, ждем 100 ms
+
 
      Sleep(100);  
+
  // Все сгенерированные выходные данные из AProcess читаются в цикле, пока больше не останется данных
    end;
+
  repeat
  end;
+
    // Получаем новые данные из процесса до максимального размера выделенного буфера.
  // читаем последний блок
+
    // Обратите внимание, что все вызовы read (...) будут блокироваться, кроме последнего, который возвращает 0 (ноль).
  repeat
+
    BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
    // убедимся, что хватает места
+
 
    M.SetSize(BytesRead + READ_BYTES);
+
    // Добавляем байты, которые были прочитаны в поток для последующего использования
    // пытаемся прочитать
+
    OutputStream.Write(Buffer, BytesRead)
    n := P.Output.Read((M.Memory + BytesRead)^, READ_BYTES);
+
 
    if n > 0  
+
  until BytesRead = 0;  // Останавливаемся, если больше нет данных
    then begin
+
 
      Inc(BytesRead, n);
+
  // Процесс завершен, поэтому его можно очистить
      Write('.');
+
  AProcess.Free;
    end;
+
 
  until n <= 0;
+
  //Теперь, когда все данные прочитаны, их можно использовать; например, чтобы сохранить их в файл на диске
  if BytesRead > 0 then WriteLn;
+
  with TFileStream.Create('output.txt', fmCreate) do
  M.SetSize(BytesRead);  
+
  begin
  WriteLn('-- executed --');
+
     OutputStream.Position := 0; // Требуется, чтобы убедиться, что все данные копируются с самого начала
 
+
    CopyFrom(OutputStream, OutputStream.Size);
  S := TStringList.Create;
+
    Free
  S.LoadFromStream(M);
+
  end;
  WriteLn('-- linecount = ', S.Count, ' --');
+
 
  for n := 0 to S.Count - 1 do
+
  // Или данные могут быть показаны на экране
  begin
+
  with TStringList.Create do
    WriteLn('| ', S[n]);
+
  begin
  end;
+
    OutputStream.Position := 0; // Требуется, чтобы убедиться, что все данные копируются с самого начала
  WriteLn('-- end --');
+
    LoadFromStream(OutputStream);
  S.Free;
+
    writeln(Text);
  P.Free;
+
    writeln('--- Номер строки = ', Count, '----');
  M.Free;
+
    Free
end.</syntaxhighlight>
+
  end;
 +
 
 +
  // Очищаем
 +
  OutputStream.Free;
 +
end.</syntaxhighlight>
 +
 
 +
 
 +
----
 +
[[User:Zoltanleo|Прим.перев.]]: можно выводить данные сразу в какой-нибудь memoOutput на основе ([https://forum.lazarus.freepascal.org/index.php/topic,17315.msg95228.html#msg95228 решения], предложенного пользователем [https://forum.lazarus.freepascal.org/index.php?action=profile;u=47822 KpjComp]):
 +
<syntaxhighlight lang=pascal>
 +
const
 +
  BUF_SIZE = 1024 * 64; // Размер буфера для чтения выходных данных, можно читать блоками по 64k
 +
var
 +
  Buffer: string = '';
 +
  BytesRead: LongInt = 0
 +
begin
 +
...
 +
  //Запуск процесса (выполнение команды dir/ls)
 +
  AProcess.Execute;
 +
 
 +
  //можно дать чуть-чуть времени на отправку данных через AProcess.Output
 +
  sleep(500);
 +
 
 +
  repeat
 +
    memoOutput.Lines.BeginUpdate;
 +
    try
 +
      SetLength(Buffer,BUF_SIZE);
 +
      BytesRead := AProcess.Output.Read(Buffer[1],Length(Buffer));
 +
      if BytesRead > 0 then
 +
      begin
 +
        SetLength(Buffer,BytesRead);
 +
        memoOutput.Append(Buffer);
 +
      end;
 +
    finally
 +
      memoOutput.Lines.EndUpdate;
 +
      memoOutput.SelStart := UTF8Length(memoOutput.Text);
 +
    end;
 +
    Sleep(50);
 +
    Application.ProcessMessages;
 +
  until BytesRead = 0;</syntaxhighlight>
 +
 
 +
----
 +
 
 +
Обратите внимание, что вышеперечисленное также может быть достигнуто с использованием RunCommand:
 +
 
 +
<syntaxhighlight lang="pascal">
 +
var s: string;
 +
...
 +
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);</syntaxhighlight>
  
 
=== Использование ввода и вывода  TProcess ===
 
=== Использование ввода и вывода  TProcess ===
Line 377: Line 446:
 
  {...}</syntaxhighlight>
 
  {...}</syntaxhighlight>
  
=== Пример "общения" с процессом aspell ===
+
=== Показ комплекта приложений на переднем плане в macOS ===
 
 
Внутри исходного кода [http://pasdoc.sourceforge.net/ pasdoc] расположены два модуля, выполняющие проверку орфографии, "общаясь" между собой посредством передачи управления процессу в котором они выполняются:
 
 
 
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas] - Данный модуль реализует класс TProcessLineTalk являющегося потомком TProcess. Он может быть использован для обмена данными с любым процессом на его основе.
 
 
 
* [http://pasdoc.svn.sourceforge.net/viewvc/*checkout*/pasdoc/trunk/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas] - Реализует класс TAspellProcess, который выполняет проверку орфографии и наследуется от TProcessLineTalk, для выполнения aspell и обращения к запустившему его процессу.
 
 
 
Оба модуля являются независимыми от остальных исходных кодов(модулей) pasdoc, поэтому они могут служить в качестве реальных примеров использования TProcess, для работы и общения посредством обмена данными с другой программой.
 
 
 
=== Показ комплекта приложений на переднем плане в OS X ===
 
  
 
Вы можете запустить '''application bundle'''(пакет приложения) через TProcess, вызвав исполняемый файл внутри пакета. Например:
 
Вы можете запустить '''application bundle'''(пакет приложения) через TProcess, вызвав исполняемый файл внутри пакета. Например:
Line 437: Line 496:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== Пример "разговора" с процессом aspell ===
+
=== Пример "общения" с процессом aspell ===
  
Внутри исходного кода [https://github.com/pasdoc/pasdoc/wiki pasdoc] вы можете найти два модуля, которые выполняют проверку орфографии, «разговаривая» с запущенным процессом aspell через каналы:
+
Внутри исходного кода [https://github.com/pasdoc/pasdoc/wiki pasdoc] вы можете найти два модуля, которые выполняют проверку орфографии, "общаясь" с запущенным процессом aspell через каналы:
  
 
* Модуль [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas] реализует класс TProcessLineTalk, потомок TProcess, который может быть легко использован для общения с любым процессом построчно.
 
* Модуль [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas] реализует класс TProcessLineTalk, потомок TProcess, который может быть легко использован для общения с любым процессом построчно.
Line 461: Line 520:
 
Process.Execute;</syntaxhighlight>
 
Process.Execute;</syntaxhighlight>
  
==== 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 - это не оболочка, а процесс. Это не два процесса, а всего один. Можно перенаправить вывод так, как вы этого пожелаете. См. [[Executing_External_Programs/ru#.D0.9A.D0.B0.D0.BA_.D0.BF.D0.B5.D1.80.D0.B5.D0.BD.D0.B0.D0.BF.D1.80.D0.B0.D0.B2.D0.B8.D1.82.D1.8C_.D0.B2.D1.8B.D0.B2.D0.BE.D0.B4_.D1.81_.D0.BF.D0.BE.D0.BC.D0.BE.D1.89.D1.8C.D1.8E_TProcess|следующий раздел]].
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]].
 
  
 
=== Как перенаправить вывод с помощью TProcess ===
 
=== Как перенаправить вывод с помощью TProcess ===
  
You can redirect the output of a command to another command by using a TProcess instance for '''each''' command.
+
Вы можете перенаправлять вывод команды другой команде, используя экземпляр TProcess для '''каждой''' команды.
  
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 ]]
+
Вот пример, который объясняет, как перенаправить вывод одного процесса другому. Чтобы перенаправить вывод процесса в файл/поток, см. пример [[Executing_External_Programs/ru#.D0.A7.D1.82.D0.B5.D0.BD.D0.B8.D0.B5_.D0.B1.D0.BE.D0.BB.D1.8C.D1.88.D0.B8.D1.85_.D0.BE.D0.B1.D1.8A.D0.B5.D0.BC.D0.BE.D0.B2_.D0.B2.D1.8B.D0.B2.D0.BE.D0.B4.D0.B0|чтение больших объемов вывода]].
  
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.
+
Вы можете не только перенаправлять «обычный» вывод (также известный как stdout), но также можете перенаправить вывод ошибок (stderr), если вы укажете опцию poStderrToOutPut, как видно в настройках для второго процесса.
  
 
<syntaxhighlight lang=pascal>program Project1;
 
<syntaxhighlight lang=pascal>program Project1;
Line 494: Line 552:
 
   SecondProcess.Executable := 'grep';  
 
   SecondProcess.Executable := 'grep';  
 
   SecondProcess.Parameters.Add(DirectorySeparator+ ' -');  
 
   SecondProcess.Parameters.Add(DirectorySeparator+ ' -');  
   // this would be the same as "pwd | grep / -"
+
   // это было бы аналогично "pwd | grep / -"
 
    
 
    
 
   FirstProcess.Execute;
 
   FirstProcess.Execute;
Line 503: Line 561:
 
     if FirstProcess.Output.NumBytesAvailable > 0 then
 
     if FirstProcess.Output.NumBytesAvailable > 0 then
 
     begin
 
     begin
       // make sure that we don't read more data than we have allocated
+
       // убеждаемся, что мы не читаем больше данных, чем мы выделили
       // in the buffer
+
       // для этого места в буфере
 
       ReadSize := FirstProcess.Output.NumBytesAvailable;
 
       ReadSize := FirstProcess.Output.NumBytesAvailable;
 
       if ReadSize > SizeOf(Buffer) then
 
       if ReadSize > SizeOf(Buffer) then
 
         ReadSize := SizeOf(Buffer);
 
         ReadSize := SizeOf(Buffer);
       // now read the output into the buffer
+
       // теперь считываем вывод в буфер
 
       ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
 
       ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
       // and write the buffer to the second process
+
       // и пишем буфер во второй процесс
 
       SecondProcess.Input.Write(Buffer[0], ReadCount);
 
       SecondProcess.Input.Write(Buffer[0], ReadCount);
 
    
 
    
       // if SecondProcess writes much data to it's Output then
+
       // если SecondProcess записывает много данных в свой Output, то
       // we should read that data here to prevent a deadlock
+
       // мы должны прочитать эти данные здесь, чтобы предотвратить блокировку
       // see the previous example "Reading Large Output"
+
       // см. предыдущий пример «Чтение больших объемов вывода»
 
     end;
 
     end;
 
   end;
 
   end;
   // Close the input on the SecondProcess
+
   // Закрываем вход на SecondProcess,
   // so it finishes processing it's data
+
   // поэтому он заканчивает обработку своих данных
 
   SecondProcess.CloseInput;
 
   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
 
   while SecondProcess.Running do
 
     Sleep(1);
 
     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
+
   // мы будем повторно использовать буфер для вывода SecondProcess'а
   // output to *this* programs stdout
+
   // вывод в *эту* программу stdout
 
   WriteLn('Grep output Start:');
 
   WriteLn('Grep output Start:');
 
   ReadSize := SecondProcess.Output.NumBytesAvailable;
 
   ReadSize := SecondProcess.Output.NumBytesAvailable;
Line 545: Line 603:
 
   WriteLn('Grep output Finish:');
 
   WriteLn('Grep output Finish:');
 
    
 
    
   // free our process objects
+
   // освобождаем объекты нашего процесса
 
   FirstProcess.Free;
 
   FirstProcess.Free;
 
   SecondProcess.Free;
 
   SecondProcess.Free;
 
end.</syntaxhighlight>
 
end.</syntaxhighlight>
  
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:
+
Этот пример может показаться излишним, поскольку можно запускать «сложные» команды, используя оболочку с 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===
+
===Перенаправление ввода и вывода и запуск под 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.
+
Распространенная проблема в Unix(macOS) и Linux заключается в том, что вы хотите выполнить какую-либо программу под учетной записью root (или, в более общем случае, под другой учетной записью пользователя). В качестве примера можно привести команду ''ping''.
  
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.
+
Если вы можете использовать sudo для этого, вы можете приспособить следующий пример, адаптированный из примера, опубликованного andyman на форуме ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). Этот пример запускает <code>ls</code> в каталоге <code>/root</code>, но, конечно, может быть адаптирован.
  
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.]
+
'''Лучший способ''' сделать это - использовать пакет policykit, который должен быть доступен во всех последних версиях Linux. Подробности смотрите в [http://lazarus.freepascal.org/index.php/topic,14479.0.html ветке форума.]
  
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.
+
Большие части этого кода похожи на предыдущий пример, но он также показывает, как перенаправить stdout и stderr процесса, вызываемого отдельно, в stdout и stderr нашего собственного кода.
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
 
program rootls;
 
program rootls;
  
{ Demonstrates using TProcess, redirecting stdout/stderr to our stdout/stderr,
+
{Демонстрирует использование TProcess, перенаправляющего stdout/stderr в наш stdout/stderr,
calling sudo on Linux/OSX, and supplying input on stdin}
+
вызов sudo в Linux/OS и обеспечивающего ввод в stdin}
 +
 
 
{$mode objfpc}{$H+}
 
{$mode objfpc}{$H+}
  
Line 587: Line 646:
 
     SudoPassword: string;
 
     SudoPassword: string;
 
   begin
 
   begin
     WriteLn('Please enter the sudo password:');
+
     WriteLn('Пожалуйста, введите пароль sudo:');
 
     Readln(SudoPassword);
 
     Readln(SudoPassword);
     ExitCode := -1; //Start out with failure, let's see later if it works
+
     ExitCode := -1; //Начнем с неудачного вывода, посмотрим позже, работает ли это
     Proc := TProcess.Create(nil); //Create a new process
+
     Proc := TProcess.Create(nil); //Создаем новый процесс
 
     try
 
     try
       Proc.Options := [poUsePipes, poStderrToOutPut]; //Use pipes to redirect program stdin,stdout,stderr
+
       Proc.Options := [poUsePipes, poStderrToOutPut]; //Используем pipes для перенаправления программных stdin, stdout, stderr
       Proc.CommandLine := 'sudo -S ls /root'; //Run ls /root as root using sudo
+
       Proc.CommandLine := 'sudo -S ls /root'; //Запускаем ls /root как root с помощью sudo
       // -S causes sudo to read the password from stdin.
+
       // -S заставляет sudo читать пароль из stdin
       Proc.Execute; //start it. sudo will now probably ask for a password
+
       Proc.Execute; //Запускаем его. sudo теперь, вероятно, попросит пароль
  
       // write the password to stdin of the sudo program:
+
       // пишем пароль в stdin sudo программы:
 
       SudoPassword := SudoPassword + LineEnding;
 
       SudoPassword := SudoPassword + LineEnding;
 
       Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
 
       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 := '%*'; // короткая строка, надеюсь, это немного зашифрует память; примечание: использование PChars более надежно
       SudoPassword := ''; // and make the program a bit safer from snooping?!?
+
       SudoPassword := ''; // и сделает программу немного более безопасной от слежки?!?
  
       // main loop to read output from stdout and stderr of sudo
+
       // основной цикл для чтения вывода из stdout и stderr sudo
 
       while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
 
       while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
 
         (Proc.Stderr.NumBytesAvailable > 0) do
 
         (Proc.Stderr.NumBytesAvailable > 0) do
 
       begin
 
       begin
         // read stdout and write to our stdout
+
         // читаем stdout и пишем в наш stdout
 
         while Proc.Output.NumBytesAvailable > 0 do
 
         while Proc.Output.NumBytesAvailable > 0 do
 
         begin
 
         begin
           ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Read up to buffer, not more
+
           ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Читаем до буфера, не более
 
           Proc.Output.Read(CharBuffer, ReadCount);
 
           Proc.Output.Read(CharBuffer, ReadCount);
 
           Write(StdOut, Copy(CharBuffer, 0, ReadCount));
 
           Write(StdOut, Copy(CharBuffer, 0, ReadCount));
 
         end;
 
         end;
         // read stderr and write to our stderr
+
         // читаем stderr и пишем в наш stderr
 
         while Proc.Stderr.NumBytesAvailable > 0 do
 
         while Proc.Stderr.NumBytesAvailable > 0 do
 
         begin
 
         begin
           ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Read up to buffer, not more
+
           ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Читаем до буфера, не более
 
           Proc.Stderr.Read(CharBuffer, ReadCount);
 
           Proc.Stderr.Read(CharBuffer, ReadCount);
 
           Write(StdErr, Copy(CharBuffer, 0, ReadCount));
 
           Write(StdErr, Copy(CharBuffer, 0, ReadCount));
Line 634: Line 693:
 
</syntaxhighlight>
 
</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.
+
Без сомнения, было бы целесообразно проверить, запрашивает ли sudo пароль. Это можно последовательно проверить, установив переменную окружения SUDO_PROMPT в ту, которую мы наблюдаем при чтении стандартного вывода TProcess, чтобы избежать проблемы, связанной с тем, что приглашение будет различным для разных локалей. Установка переменной среды приводит к тому, что значения по умолчанию очищаются (наследуются от нашего процесса), поэтому мы должны при необходимости скопировать окружение из нашей программы.
  
=== Using fdisk with sudo on Linux ===
+
=== Использование fdisk с sudo в 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.'''
+
В следующем примере показано, как запустить fdisk на компьютере с Linux с помощью команды sudo для получения прав root.  
 +
{{Note| это только пример, и он не рассчитан на большой результат}}
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
 
program getpartitioninfo;
 
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}
+
{Первоначально пример предоставлен пользователем wjackson153 с форума Lazarus. Пожалуйста, свяжитесь с ним в случае возникновения вопросов, замечаний и т.д. Пример преобразован для простоты понимания/краткости из фрагмента кода Lazarus в программу FPC пользователем BigChimp}
  
 
Uses
 
Uses
Line 654: Line 714:
  
 
begin   
 
begin   
   sPass := 'yoursudopasswordhere'; // You need to change this to your own sudo password
+
   sPass := 'yoursudopasswordhere'; // Вы должны изменить это на свой собственный пароль sudo
   OutputLines:=TStringList.Create; //... a try...finally block would be nice to make sure
+
   OutputLines:=TStringList.Create; //... было бы неплохо убедиться с помощью блока try..finally,
   // OutputLines is freed... Same for hProcess.
+
   // что OutputLines освобожден ... То же самое для hProcess.
 
      
 
      
   // The following example will open fdisk in the background and give us partition information
+
   // Следующий пример откроет fdisk в фоновом режиме и даст нам информацию о разделе
   // Since fdisk requires elevated priviledges we need to
+
   // Поскольку fdisk требует повышенных привилегий, нам нужно
   // pass our password as a parameter to sudo using the -S
+
   // передать наш пароль в качестве параметра sudo с помощью параметра -S,
   // option, so it will will wait till our program sends our password to the sudo application
+
   // поэтому он будет ждать, пока наша программа отправит наш пароль в sudo приложение
 
   hProcess := TProcess.Create(nil);
 
   hProcess := TProcess.Create(nil);
   // On Linux/Unix/OSX, we need specify full path to our executable:
+
   // В Linux/Unix/macOS нам нужно указать полный путь к нашему исполняемому файлу:
 
   hProcess.Executable := '/bin/sh';
 
   hProcess.Executable := '/bin/sh';
   // Now we add all the parameters on the command line:
+
   // Теперь мы добавляем все параметры в командную строку:
 
   hprocess.Parameters.Add('-c');
 
   hprocess.Parameters.Add('-c');
   // Here we pipe the password to the sudo command which then executes fdisk -l:  
+
   // Здесь мы передаем пароль в команду sudo, которая затем выполняет fdisk -l:  
 
   hprocess.Parameters.add('echo ' + sPass  + ' | sudo -S 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
+
   // Запускаем асинхронно (дожидаемся завершения процесса) и используем pipes, чтобы мы могли прочитать output pipe
 
   hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
 
   hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
   // Now run:
+
   // Теперь запускаем:
 
   hProcess.Execute;
 
   hProcess.Execute;
  
   // hProcess should have now run the external executable (because we use poWaitOnExit).
+
   // hProcess должен запустить внешний исполняемый файл (потому что мы используем poWaitOnExit).
   // Now you can process the process output (standard output and standard error), eg:
+
   // Теперь вы можете обработать output процесса (стандартный output и стандартный error), например:
 
   OutputLines.Add('stdout:');
 
   OutputLines.Add('stdout:');
 
   OutputLines.LoadFromStream(hprocess.Output);
 
   OutputLines.LoadFromStream(hprocess.Output);
 
   OutputLines.Add('stderr:');
 
   OutputLines.Add('stderr:');
 
   OutputLines.LoadFromStream(hProcess.Stderr);
 
   OutputLines.LoadFromStream(hProcess.Stderr);
   // Show output on screen:
+
   // Показываем output на экране:
 
   writeln(OutputLines.Text);
 
   writeln(OutputLines.Text);
  
   // Clean up to avoid memory leaks:
+
   // Очищаем, чтобы избежать утечек памяти:
 
   hProcess.Free;
 
   hProcess.Free;
 
   OutputLines.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('ping -c 1 www.google.com');
 
   //hprocess.Parameters.Add('ifconfig wlan0 | grep ' +  QuotedStr('inet addr:') + ' | cut -d: -f2');
 
   //hprocess.Parameters.Add('ifconfig wlan0 | grep ' +  QuotedStr('inet addr:') + ' | cut -d: -f2');
  
   //Using QuotedStr() is not a requirement though it makes for cleaner code;
+
   //Использование QuotedStr() не является обязательным требованием, хотя делает его более читабельным кодом;
   //you can use double quote and have the same effect.
+
   //Вы можете использовать двойные кавычки и добиться того же эффекта
  
 
   //hprocess.Parameters.Add('glxinfo | grep direct');   
 
   //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');  
 
   //hprocess.Parameters.add('echo ' + sPass  + ' | sudo -S apt-get install -y pkg-name');  
Line 705: Line 765:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
===Parameters which contain spaces (Replacing Shell Quotes)===
+
===Параметры, содержащие пробелы (замена кавычек оболочки Shell)===
  
In the Linux shell it is possible to write quoted arguments like this:
+
В оболочке Linux можно записать в кавычки следующие аргументы:
  
 
<syntaxhighlight lang=bash>gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram</syntaxhighlight>
 
<syntaxhighlight lang=bash>gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram</syntaxhighlight>
  
And GDB will receive 3 arguments (in addition to the first argument which is the full path to the executable):
+
И GDB получит 3 аргумента (в дополнение к первому аргументу, который является полным путем к исполняемому файлу):
 
#--batch
 
#--batch
 
#--eval-command=info symbol 0x0000DDDD
 
#--eval-command=info symbol 0x0000DDDD
#the full path to myprogram
+
#полный путь к 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 также может передавать параметры, содержащие пробелы, но использует другой стиль квотирования. Вместо того, чтобы заключать в кавычки только часть параметра, заключите в кавычки все из них. Вот так:
  
 
<syntaxhighlight lang=bash>TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';</syntaxhighlight>
 
<syntaxhighlight lang=bash>TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';</syntaxhighlight>
  
And also remember to only pass full paths.
+
И также не забудьте передать только полный путь.
  
See also this discussion about it: http://bugs.freepascal.org/view.php?id=14446
+
Смотрите также эту дискуссию об этом: [http://bugs.freepascal.org/view.php?id=14446 здесь]
  
 
==Альтернативные решения с помощью LCLIntf==
 
==Альтернативные решения с помощью LCLIntf==
Иногда вам нет необходимости явно вызывать внешнюю программу для получения необходимой функциональности. Вместо запуска приложения и передачи в него документа, просто выполните запрос через операционную систему открыть документ и использовать для этого приложение по-умолчанию, ассоциированное с данным типом файлов. Ниже представлены некоторые примеры.
+
Иногда вам не нужно явно вызывать внешнюю программу, чтобы получить необходимую вам функциональность. Вместо того, чтобы открывать приложение и указывать документ для него, просто попросите ОС открыть документ, позволив ей использовать приложение по умолчанию, связанное с этим типом файла. Ниже приведены некоторые примеры.
  
===Открыть документ в приложении по умолчанию===
+
===Открытие документа в приложении по умолчанию===
  
В некоторых ситуациях вам необходимо открыть документ/файл в приложении, используемом по умолчанию для данного типа документов. Это зависит от того, в какой операционной системе выполняется приложение.
+
В некоторых ситуациях вам нужно открыть какой-либо документ/файл с использованием приложения по умолчанию, а не выполнить определенную программу. Это зависит от запущенной операционной системы. Lazarus предоставляет платформ-независимую процедуру '''OpenDocument''', которая будет делать это за вас. Ваше приложение продолжит работу, не дожидаясь закрытия процесса документа.
Lazarus предоставляет платформонезависимую процедуру '''OpenDocument''', которая будет обрабатывать такой запрос. Ваше приложение будет продолжать работать, не дожидаясь закрытия документа в вызываемом приложении.
 
  
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
Line 741: Line 800:
 
* [[opendocument|справка по OpenDocument]]
 
* [[opendocument|справка по OpenDocument]]
  
===Открыть страницу в браузере по умолчанию===
+
===Открытие web-страницы в web-браузере по умолчанию===
 +
 
 +
Просто передайте требуемый URL, ''http://'' в начале строки является необязательным при определенных условиях.  Кроме того, передача имени файла дает те же результаты, что и OpenDocument()
  
Просто передайте необходимый URL; при этом указание ''http://'' при определенных обстоятельствах является необязательным. Передача имени файла дает такой же результат, как и использование OpenDocument()
 
 
<syntaxhighlight lang=pascal>
 
<syntaxhighlight lang=pascal>
 
uses LCLIntf;
 
uses LCLIntf;
Line 751: Line 811:
  
 
См. также:
 
См. также:
* [[openurl|справка по OpenURL]]
+
* [[OpenURL|справка по OpenURL]]
  
 
Кроме того, вы можете использовать '''TProcess''' как в данном примере:
 
Кроме того, вы можете использовать '''TProcess''' как в данном примере:
Line 758: Line 818:
  
 
procedure OpenWebPage(URL: string);
 
procedure OpenWebPage(URL: string);
// Apparently you need to pass your URL inside ", like "www.lazarus.freepascal.org"
+
// Очевидно, вам нужно передать свой URL внутри кавычек "", например, "www.lazarus.freepascal.org"
 
var
 
var
 
   Browser, Params: string;
 
   Browser, Params: string;
Line 767: Line 827:
 
     Executable := Browser;
 
     Executable := Browser;
 
     Params:=Format(Params, [URL]);
 
     Params:=Format(Params, [URL]);
     Params:=copy(Params,2,length(Params)-2); // remove "", the new version of TProcess.Parameters does that itself
+
     Params:=copy(Params,2,length(Params)-2); // удаляем "", новая версия TProcess.Parameters делает это сама
 
     Parameters.Add(Params);
 
     Parameters.Add(Params);
 
     Options := [poNoConsole];
 
     Options := [poNoConsole];
Line 779: Line 839:
 
==См. также==
 
==См. также==
 
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html Документация по TProcess]
 
* [http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html Документация по TProcess]
* [[opendocument]]
+
* [[opendocument|OpenDocument]]
* [[openurl]]
+
* [[OpenURL]]
 
* [[TProcessUTF8/ru|TProcessUTF8]]
 
* [[TProcessUTF8/ru|TProcessUTF8]]
 
* [[TXMLPropStorage/ru|TXMLPropStorage]]
 
* [[TXMLPropStorage/ru|TXMLPropStorage]]
 
* [[Webbrowser/ru|Webbrowser]]
 
* [[Webbrowser/ru|Webbrowser]]
 
+
* [http://freepascal.ru/article/freepascal/20191121080000/ Запуск чужой программы из своей] - статья на русскоязычном freepascal-ресурсе
[[Category:Tutorials/ru]]
 
[[Category:Parallel programming/ru]]
 
[[Category:FPC/ru]]
 
[[Category:Lazarus/ru]]
 
[[Category:Inter-process communication/ru]]
 
[[Category:Russian (unfinished translation)]]
 

Revision as of 05:29, 1 April 2021

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

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

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

Метод Библиотека Платформы Одной строкой? Особенности
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, что это за команда для выполнения.
   // Давайте использовать компилятор Free Pascal (версия i386)
   AProcess.Executable:= 'ppc386';

   // Передаеv -h вместе с ppc386, чтобы фактически выполнить 'ppc386 -h':
   AProcess.Parameters.Add('-h');
 
   // Мы определим опцию, когда программа 
   // будет запущена. Эта опция гарантирует, что наша программа 
   // не продолжит работу, пока программа, которую мы запустили, 
   // не перестанет работать. vvvvvvvvvvvvvv
   AProcess.Options := AProcess.Options + [poWaitOnExit];
 
   // Теперь пусть Process запустит программу
   AProcess.Execute;
 
   // Пока ppc386 не прекратит работу, мы досюда не дойдем
   AProcess.Free;   
 end.

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



Прим.перев.: на момент правки статьи (fpc 3.0.4) свойство CommandLine (как и ApplicationName) согласно документации объявлены deprecated. Вместо них рекомендуется использовать свойства Parameters и Executable соответственно.

Light bulb  Примечание: для пользователей Windows - если путь к исполняемому файлу включает символы пробела, табуляции или перевода строки, то он должен заключаться в двойные кавычки (например, "c:\Program Files\Git\bin\git.exe"). Это же правило справедливо в случае, если такой путь указывается в аргументах командной строки (в свойстве Parameters).
Light bulb  Примечание: строковый агрумент, добавляемый в Parameters, не должен содержать пробелы.
...
// к примеру, один из аргументов командной строки должен быть '-user sysdba'
//тогда он должен быть записан так
AProcess.Parameters.Add('-user');
AProcess.Parameters.Add('sysdba');
...

Усовершенствованный пример (но пока не правильный)

Это все замечательно, но как я могу получить вывод программы, которую я запустил? Что ж, давайте немного расширим наш пример и сделаем так: этот пример прост, поэтому вы можете извлечь из него уроки. Пожалуйста, не используйте этот пример в рабочем коде, а используйте код из раздела Чтение больших объемов вывода

// Это 
// ДЕФЕКТНАЯ
// демонстрационная программа, которая показывает
// как запустить внешнюю программу
// и читать из ее вывода.
program launchprogram;
 
// Здесь мы включаем файлы, которые имеют полезные функции
// и процедуры, и которые нам понадобятся.
uses 
  Classes, SysUtils, Process;
 
// Это определение переменной «AProcess», как переменной
// типа "TProcess".
// Также еще мы добавляем TStringList для хранения
// данных, читаемых из вывода программ.
var 
  AProcess: TProcess;
  AStringList: TStringList;

// Это место, где наша программа начинает работать
begin
  // Теперь мы создадим объект Process и 
  // назначим его переменной var AProcess.
  AProcess := TProcess.Create(nil);
 
  // Сообщим новому AProcess, что это за команда для выполнения.
  AProcess.Executable := '/usr/bin/ppc386'; 
  AProcess.Parameters.Add('-h'); 

  // Мы определим опцию, когда программа
  // выполняется. Эта опция гарантирует, что наш AProcess
  // не будет продолжаться до тех пор, пока запущенная нами программа
  // остановится. Также теперь мы сообщим AProcess, что
  // мы хотим прочитать вывод файла.
  AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
 
  // Теперь, когда AProcess знает, что это за командная строка, его можно запустить.
  AProcess.Execute;
  
  // После завершения AProcess, остальная часть программы будет выполнена.
 
  // Теперь читаем вывод программы, который мы только что запихали в TStringList.
  AStringList := TStringList.Create;
  AStringList.LoadFromStream(AProcess.Output);
   
  // Сохраняем выходные данные в файл и очищаем TStringList.
  AStringList.SaveToFile('output.txt');
  AStringList.Free;
 
  // Теперь, когда выходные данные процесса обработаны, его можно освободить.
  AProcess.Free;   
end.

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

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

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

Если вы хотите считывать выходные данные из внешнего процесса, вы должны адаптировать этот код для повседневного использования.

 program LargeOutputDemo;

{$mode objfpc}{$H+}
 
 uses
   Classes, Process, SysUtils; // Process - модуль, содержащий TProcess

const
  BUF_SIZE = 2048; // Размер буфера для чтения выходных данных блоками
var
  AProcess     : TProcess;
  OutputStream : TStream;
  BytesRead    : longint;
  Buffer       : array[1..BUF_SIZE] of byte;

begin
  // Установки процесса; в качестве примера используется рекурсивный поиск в каталоге,
  // потому что это обычно приводит к большому количеству данных.
  AProcess := TProcess.Create(nil);

  //Команды для Windows и *nix отличаются, поэтому используется директива $IFDEFs
  {$IFDEF Windows}
    // В Windows команду dir нельзя использовать напрямую, потому что она является встроенной
    // командой оболочки. Поэтому нужны cmd.exe и дополнительные параметры
    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}

  // Необходимо использовать poUsePipes - параметр TProcess, чтобы можно было получить выходные данные.
  // Параметр TProcess'а poWaitOnExit не может быть использован, потому что он заблокирует
  // эту программу до окончания вызванного процесса, препятствуя чтению выходных данных процесса.
  AProcess.Options := [poUsePipes];

  //Запуск процесса (выполнение команды dir/ls)
  AProcess.Execute;

  // Создаем объект потока для хранения сгенерированного вывода. Вывод может
  // также являться файловым потоком для непосредственного сохранения выводимых данных на диск.
  OutputStream := TMemoryStream.Create;

  // Все сгенерированные выходные данные из AProcess читаются в цикле, пока больше не останется данных
  repeat
    // Получаем новые данные из процесса до максимального размера выделенного буфера.
    // Обратите внимание, что все вызовы read (...) будут блокироваться, кроме последнего, который возвращает 0 (ноль).
    BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);

    // Добавляем байты, которые были прочитаны в поток для последующего использования
    OutputStream.Write(Buffer, BytesRead)

  until BytesRead = 0;  // Останавливаемся, если больше нет данных

  // Процесс завершен, поэтому его можно очистить
  AProcess.Free;

  //Теперь, когда все данные прочитаны, их можно использовать; например, чтобы сохранить их в файл на диске
  with TFileStream.Create('output.txt', fmCreate) do
  begin
    OutputStream.Position := 0; // Требуется, чтобы убедиться, что все данные копируются с самого начала
    CopyFrom(OutputStream, OutputStream.Size);
    Free
  end;

  // Или данные могут быть показаны на экране
  with TStringList.Create do
  begin
    OutputStream.Position := 0; // Требуется, чтобы убедиться, что все данные копируются с самого начала
    LoadFromStream(OutputStream);
    writeln(Text);
    writeln('--- Номер строки = ', Count, '----');
    Free
  end;

  // Очищаем
  OutputStream.Free;
end.



Прим.перев.: можно выводить данные сразу в какой-нибудь memoOutput на основе (решения, предложенного пользователем KpjComp):

const
  BUF_SIZE = 1024 * 64; // Размер буфера для чтения выходных данных, можно читать блоками по 64k
var
  Buffer: string = '';
  BytesRead: LongInt = 0;   
begin
...
  //Запуск процесса (выполнение команды dir/ls)
  AProcess.Execute;
  
  //можно дать чуть-чуть времени на отправку данных через AProcess.Output 
  sleep(500);
  
  repeat
    memoOutput.Lines.BeginUpdate;
    try
      SetLength(Buffer,BUF_SIZE);
      BytesRead := AProcess.Output.Read(Buffer[1],Length(Buffer));
      if BytesRead > 0 then
      begin
        SetLength(Buffer,BytesRead);
        memoOutput.Append(Buffer);
      end;
    finally
      memoOutput.Lines.EndUpdate;
      memoOutput.SelStart := UTF8Length(memoOutput.Text);
    end;
    Sleep(50);
    Application.ProcessMessages;
  until BytesRead = 0;

Обратите внимание, что вышеперечисленное также может быть достигнуто с использованием RunCommand:

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

Использование ввода и вывода 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
 {...}

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

Вы можете запустить 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 каждого процесса в отдельности, что может быть очень выгодно для вашего проекта.

Перенаправление ввода и вывода и запуск под Root

Распространенная проблема в Unix(macOS) и Linux заключается в том, что вы хотите выполнить какую-либо программу под учетной записью root (или, в более общем случае, под другой учетной записью пользователя). В качестве примера можно привести команду ping.

Если вы можете использовать sudo для этого, вы можете приспособить следующий пример, адаптированный из примера, опубликованного andyman на форуме ([1]). Этот пример запускает ls в каталоге /root, но, конечно, может быть адаптирован.

Лучший способ сделать это - использовать пакет policykit, который должен быть доступен во всех последних версиях Linux. Подробности смотрите в ветке форума.

Большие части этого кода похожи на предыдущий пример, но он также показывает, как перенаправить stdout и stderr процесса, вызываемого отдельно, в stdout и stderr нашего собственного кода.

program rootls;

{Демонстрирует использование TProcess, перенаправляющего stdout/stderr в наш stdout/stderr,
вызов sudo в Linux/OS и обеспечивающего ввод в 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('Пожалуйста, введите пароль sudo:');
    Readln(SudoPassword);
    ExitCode := -1; //Начнем с неудачного вывода, посмотрим позже, работает ли это
    Proc := TProcess.Create(nil); //Создаем новый процесс
    try
      Proc.Options := [poUsePipes, poStderrToOutPut]; //Используем pipes для перенаправления программных stdin, stdout, stderr
      Proc.CommandLine := 'sudo -S ls /root'; //Запускаем ls /root как root с помощью sudo
      // -S заставляет sudo читать пароль из stdin
      Proc.Execute; //Запускаем его. sudo теперь, вероятно, попросит пароль

      // пишем пароль в stdin sudo программы:
      SudoPassword := SudoPassword + LineEnding;
      Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
      SudoPassword := '%*'; // короткая строка, надеюсь, это немного зашифрует память; примечание: использование PChars более надежно
      SudoPassword := ''; // и сделает программу немного более безопасной от слежки?!?

      // основной цикл для чтения вывода из stdout и stderr sudo
      while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
        (Proc.Stderr.NumBytesAvailable > 0) do
      begin
        // читаем stdout и пишем в наш stdout
        while Proc.Output.NumBytesAvailable > 0 do
        begin
          ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Читаем до буфера, не более
          Proc.Output.Read(CharBuffer, ReadCount);
          Write(StdOut, Copy(CharBuffer, 0, ReadCount));
        end;
        // читаем stderr и пишем в наш stderr
        while Proc.Stderr.NumBytesAvailable > 0 do
        begin
          ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Читаем до буфера, не более
          Proc.Stderr.Read(CharBuffer, ReadCount);
          Write(StdErr, Copy(CharBuffer, 0, ReadCount));
        end;
      end;
      ExitCode := Proc.ExitStatus;
    finally
      Proc.Free;
      Halt(ExitCode);
    end;
  end;

begin
  RunsLsRoot;
end.

Другие мысли: Без сомнения, было бы целесообразно проверить, запрашивает ли sudo пароль. Это можно последовательно проверить, установив переменную окружения SUDO_PROMPT в ту, которую мы наблюдаем при чтении стандартного вывода TProcess, чтобы избежать проблемы, связанной с тем, что приглашение будет различным для разных локалей. Установка переменной среды приводит к тому, что значения по умолчанию очищаются (наследуются от нашего процесса), поэтому мы должны при необходимости скопировать окружение из нашей программы.

Использование fdisk с sudo в Linux

В следующем примере показано, как запустить fdisk на компьютере с Linux с помощью команды sudo для получения прав root.

Light bulb  Примечание: это только пример, и он не рассчитан на большой результат
program getpartitioninfo;

{Первоначально пример предоставлен пользователем wjackson153 с форума Lazarus. Пожалуйста, свяжитесь с ним в случае возникновения вопросов, замечаний и т.д. Пример преобразован для простоты понимания/краткости из фрагмента кода Lazarus в программу FPC пользователем BigChimp}

Uses
  Classes, SysUtils, FileUtil, Process;

var
  hprocess: TProcess;
  sPass: String;
  OutputLines: TStringList;

begin  
  sPass := 'yoursudopasswordhere'; // Вы должны изменить это на свой собственный пароль sudo
  OutputLines:=TStringList.Create; //... было бы неплохо убедиться с помощью блока try..finally, 
  // что OutputLines освобожден ... То же самое для hProcess.
     
  // Следующий пример откроет fdisk в фоновом режиме и даст нам информацию о разделе
  // Поскольку fdisk требует повышенных привилегий, нам нужно 
  // передать наш пароль в качестве параметра sudo с помощью параметра -S,
  // поэтому он будет ждать, пока наша программа отправит наш пароль в sudo приложение
  hProcess := TProcess.Create(nil);
  // В Linux/Unix/macOS нам нужно указать полный путь к нашему исполняемому файлу:
  hProcess.Executable := '/bin/sh';
  // Теперь мы добавляем все параметры в командную строку:
  hprocess.Parameters.Add('-c');
  // Здесь мы передаем пароль в команду sudo, которая затем выполняет fdisk -l: 
  hprocess.Parameters.add('echo ' + sPass  + ' | sudo -S fdisk -l');
  // Запускаем асинхронно (дожидаемся завершения процесса) и используем pipes, чтобы мы могли прочитать output pipe
  hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
  // Теперь запускаем:
  hProcess.Execute;

  // hProcess должен запустить внешний исполняемый файл (потому что мы используем poWaitOnExit).
  // Теперь вы можете обработать output процесса (стандартный output и стандартный error), например:
  OutputLines.Add('stdout:');
  OutputLines.LoadFromStream(hprocess.Output);
  OutputLines.Add('stderr:');
  OutputLines.LoadFromStream(hProcess.Stderr);
  // Показываем output на экране:
  writeln(OutputLines.Text);

  // Очищаем, чтобы избежать утечек памяти:
  hProcess.Free;
  OutputLines.Free;
  
  //Ниже приведены некоторые примеры, как вы видите, мы можем передавать недопустимые символы, как если бы это было сделано из терминала
  //Даже, если вы где-то читали, что это невозможно, я уверяю вас, что при помощи этого метода вы сможете :)

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

  //Использование QuotedStr() не является обязательным требованием, хотя делает его более читабельным кодом;
  //Вы можете использовать двойные кавычки и добиться того же эффекта

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

  // Этот метод также можно использовать для установки приложений из вашего хранилища:

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

 end.

Параметры, содержащие пробелы (замена кавычек оболочки Shell)

В оболочке Linux можно записать в кавычки следующие аргументы:

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

И GDB получит 3 аргумента (в дополнение к первому аргументу, который является полным путем к исполняемому файлу):

  1. --batch
  2. --eval-command=info symbol 0x0000DDDD
  3. полный путь к myprogram

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

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

И также не забудьте передать только полный путь.

Смотрите также эту дискуссию об этом: здесь

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

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

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

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

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

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

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

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

См. также:

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

uses Process;

procedure OpenWebPage(URL: string);
// Очевидно, вам нужно передать свой URL внутри кавычек "", например, "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); // удаляем "", новая версия TProcess.Parameters делает это сама
    Parameters.Add(Params);
    Options := [poNoConsole];
    Execute;
  finally
    Free;
  end;
end;

См. также