Difference between revisions of "Executing External Programs/ru"
(54 intermediate revisions by 2 users not shown) | |||
Line 81: | Line 81: | ||
SysUtils.ExecuteProcess('/full/path/to/binary',['arg1','arg2']); | SysUtils.ExecuteProcess('/full/path/to/binary',['arg1','arg2']); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | При использовании этого метода, приложение "повисает" до тех пор, пока вызванная программа не завершится. Это может быть полезно если пользователь должен сделать что-то перед использованием вашего приложения. Если вы хотите обойти это ограничение, то | + | При использовании этого метода, приложение "повисает" до тех пор, пока вызванная программа не завершится. Это может быть полезно если пользователь должен сделать что-то перед использованием вашего приложения. Если вы хотите обойти это ограничение, то более полезный кроссплатформенный способ - это '''TProcess''', или если ваша целевая платформа только Windows, то используйте '''ShellExecute'''. |
== MS Windows : CreateProcess, ShellExecute и WinExec == | == MS Windows : CreateProcess, ShellExecute и WinExec == | ||
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* ( | + | Если в Delphi вы используете ShellExecute для открытия документов, например документ Word или ссылки, то присмотритесь к open* (OpenURL и т.д.) модуле lclintf. |
− | === Использование ShellExecuteEx с повышенными ( | + | === Использование ShellExecuteEx с повышенными правами (администратора)=== |
Если вы хотите использовать программу с повышенными (правами администратора) правами, используйте '''runas''' как альтернативу ShellExecuteEx: | Если вы хотите использовать программу с повышенными (правами администратора) правами, используйте '''runas''' как альтернативу ShellExecuteEx: | ||
<syntaxhighlight lang=pascal> | <syntaxhighlight lang=pascal> | ||
Line 164: | Line 164: | ||
*Способность читать из stdout и писать в stdin. | *Способность читать из stdout и писать в stdin. | ||
− | + | {{Note| TProcess не оболочка! И не терминал! Вы не можете напрямую исполнять скрипты или перенаправлять вывод используя такие операторы, как "|", ">", "<", "&" и т.д. Но возможно получить те же самые результаты используя TProcess, далее будут приведены некоторые примеры.}} | |
− | Важно: Вы должны определять полный путь к исполняемому файлу. Например '/bin/cp' вместо 'cp'. Если программа находится где-либо в переменной PATH, то вы можете использовать функцию [ | + | Важно: Вы должны определять полный путь к исполняемому файлу. Например '/bin/cp' вместо 'cp'. Если программа находится где-либо в переменной PATH, то вы можете использовать функцию [https://lazarus-ccr.sourceforge.io/docs/lazutils/fileutil/finddefaultexecutablepath.html FindDefaultExecutablePath] из модуля LCL [https://lazarus-ccr.sourceforge.io/docs/lazutils/fileutil/index.html FileUtil]. |
+ | |||
+ | === Простейший пример === | ||
+ | |||
+ | Многие типичные случаи были подготовлены в функциях [[Executing_External_Programs#.28Process..29RunCommand|Runcommand]]. Прежде, чем начать копировать и вставлять приведенные ниже примеры, сначала проверьте их. | ||
=== Простой пример === | === Простой пример === | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
// Демо-программа, показывающая, как можно запустить | // Демо-программа, показывающая, как можно запустить | ||
// внешнюю программу | // внешнюю программу | ||
Line 190: | Line 194: | ||
AProcess := TProcess.Create(nil); | AProcess := TProcess.Create(nil); | ||
− | // | + | // Сообщаем новому AProcess, что это за команда для выполнения. |
− | + | // Давайте использовать компилятор Free Pascal (версия i386) | |
− | AProcess. | + | AProcess.Executable:= 'ppc386'; |
+ | |||
+ | // Передаеv -h вместе с ppc386, чтобы фактически выполнить 'ppc386 -h': | ||
+ | AProcess.Parameters.Add('-h'); | ||
− | // | + | // Мы определим опцию, когда программа |
− | // Эта опция не | + | // будет запущена. Эта опция гарантирует, что наша программа |
− | // | + | // не продолжит работу, пока программа, которую мы запустили, |
+ | // не перестанет работать. vvvvvvvvvvvvvv | ||
AProcess.Options := AProcess.Options + [poWaitOnExit]; | AProcess.Options := AProcess.Options + [poWaitOnExit]; | ||
− | // Теперь | + | // Теперь пусть Process запустит программу |
− | |||
AProcess.Execute; | AProcess.Execute; | ||
Line 210: | 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> | ||
+ | ... | ||
+ | // к примеру, один из аргументов командной строки должен быть '-user sysdba' | ||
+ | //тогда он должен быть записан так | ||
+ | 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"> | |
− | + | // Это | |
− | <syntaxhighlight> | + | // ДЕФЕКТНАЯ |
− | + | // демонстрационная программа, которая показывает | |
− | + | // как запустить внешнюю программу | |
− | + | // и читать из ее вывода. | |
+ | 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.</syntaxhighlight> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | </syntaxhighlight> | ||
=== Чтение больших объемов вывода === | === Чтение больших объемов вывода === | ||
Line 271: | Line 298: | ||
Поэтому следующий пример не будет использовать опцию poWaitOnExit и будет читать данные при запущенной программе. Вывод записывается в поток, который можно позже использовать для чтения его содержимого в TStringList. | Поэтому следующий пример не будет использовать опцию poWaitOnExit и будет читать данные при запущенной программе. Вывод записывается в поток, который можно позже использовать для чтения его содержимого в TStringList. | ||
− | <syntaxhighlight> program | + | Если вы хотите считывать выходные данные из внешнего процесса, вы должны адаптировать этот код для повседневного использования. |
− | + | ||
− | + | <syntaxhighlight lang=pascal> program LargeOutputDemo; | |
− | + | ||
− | + | {$mode objfpc}{$H+} | |
− | |||
− | |||
uses | uses | ||
− | Classes, Process, SysUtils; | + | 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.</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 === | ||
− | Смотри демо пример на [https:// | + | Смотри демо пример на [https://sourceforge.net/p/lazarus-ccr/svn/913/tree/examples/process Lazarus-CCR SVN]. |
=== Некоторые подсказки при использовании TProcess === | === Некоторые подсказки при использовании TProcess === | ||
Line 362: | Line 435: | ||
Например: | Например: | ||
− | <syntaxhighlight> {...} | + | <syntaxhighlight lang=pascal> {...} |
AProcess:TProcess.Create(nil) | AProcess:TProcess.Create(nil) | ||
{$IFDEF WIN32} | {$IFDEF WIN32} | ||
Line 373: | Line 446: | ||
{...}</syntaxhighlight> | {...}</syntaxhighlight> | ||
− | === | + | === Показ комплекта приложений на переднем плане в macOS === |
− | + | Вы можете запустить '''application bundle'''(пакет приложения) через TProcess, вызвав исполняемый файл внутри пакета. Например: | |
− | + | <syntaxhighlight lang=pascal> | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | <syntaxhighlight> | ||
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal'; | AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal'; | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Это запустит ''Calendar'', но окно будет позади текущего приложения. | |
− | + | Чтобы получить приложение на переднем плане, вы можете использовать утилиту '''open''' с параметром '''-n''': | |
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
AProcess.Executable:='/usr/bin/open'; | AProcess.Executable:='/usr/bin/open'; | ||
AProcess.Parameters.Add('-n'); | AProcess.Parameters.Add('-n'); | ||
− | AProcess.Parameters.Add('-a'); // | + | AProcess.Parameters.Add('-a'); //необязательно: скрыть терминал - аналогично опции Windows poNoConsole |
AProcess.Parameters.Add('/Application/iCal.app'); | AProcess.Parameters.Add('/Application/iCal.app'); | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Если вашему приложению нужны параметры, вы можете передать '''open''' параметр '''--args''', после чего все параметры будут переданы приложению: | |
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
AProcess.Parameters.Add('--args'); | AProcess.Parameters.Add('--args'); | ||
AProcess.Parameters.Add('argument1'); | AProcess.Parameters.Add('argument1'); | ||
Line 406: | Line 469: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | === | + | === Запуск отдельной программы === |
− | + | Обычно программа, запускаемая вашим приложением, является дочерним процессом и уничтожается, когда ваше приложение уничтожается. Если вы хотите запустить автономную программу, которая продолжает работать [после завершения вашего приложения], вы можете использовать следующее: | |
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
var | var | ||
Process: TProcess; | Process: TProcess; | ||
Line 421: | Line 484: | ||
Process.ShowWindow := swoShow; | Process.ShowWindow := swoShow; | ||
− | // | + | // Копируем переменные среды по умолчанию, включая переменную DISPLAY, для работы приложения с графическим интерфейсом |
for I := 1 to GetEnvironmentVariableCount do | for I := 1 to GetEnvironmentVariableCount do | ||
Process.Environment.Add(GetEnvironmentString(I)); | Process.Environment.Add(GetEnvironmentString(I)); | ||
Line 433: | Line 496: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | === | + | === Пример "общения" с процессом aspell === |
− | + | Внутри исходного кода [https://github.com/pasdoc/pasdoc/wiki pasdoc] вы можете найти два модуля, которые выполняют проверку орфографии, "общаясь" с запущенным процессом aspell через каналы: | |
− | * [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_ProcessLineTalk.pas PasDoc_ProcessLineTalk.pas | + | * Модуль [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_Aspell.pas PasDoc_Aspell.pas | + | * Модуль [https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_Aspell.pas PasDoc_Aspell.pas] реализует класс TAspellProcess, который выполняет проверку орфографии с использованием базового экземпляра TProcessLineTalk для выполнения aspell и связи с запущенным процессом aspell. |
− | + | Оба модуля довольно независимы от остальных исходников pasdoc, поэтому они могут служить реальными примерами использования TProcess для запуска и связи по каналам с другой программой. | |
− | === | + | === Замена операторов командной оболочки, таких как "| < >" === |
− | + | Иногда у вас есть желание выполнить более сложную команду, которая передает свои данные другой команде или файлу. | |
− | + | Что-то вроде | |
− | <syntaxhighlight>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight> | + | <syntaxhighlight lang=bash>ShellExecute('firstcommand.exe | secondcommand.exe');</syntaxhighlight> |
− | + | или | |
− | <syntaxhighlight>ShellExecute('dir > output.txt');</syntaxhighlight> | + | <syntaxhighlight lang=bash>ShellExecute('dir > output.txt');</syntaxhighlight> |
− | + | Запуск таких команд с TProcess не будет работать. То есть: | |
− | <syntaxhighlight>// | + | <syntaxhighlight lang=pascal>// так работать не будет |
Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; | Process.CommandLine := 'firstcommand.exe | secondcommand.exe'; | ||
Process.Execute;</syntaxhighlight> | Process.Execute;</syntaxhighlight> | ||
− | ==== | + | ==== Почему использование специальных операторов для перенаправления вывода не работает ==== |
− | TProcess | + | 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|следующий раздел]]. |
− | |||
− | === | + | === Как перенаправить вывод с помощью TProcess === |
− | + | Вы можете перенаправлять вывод команды другой команде, используя экземпляр TProcess для '''каждой''' команды. | |
− | + | Вот пример, который объясняет, как перенаправить вывод одного процесса другому. Чтобы перенаправить вывод процесса в файл/поток, см. пример [[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|чтение больших объемов вывода]]. | |
− | + | Вы можете не только перенаправлять «обычный» вывод (также известный как stdout), но также можете перенаправить вывод ошибок (stderr), если вы укажете опцию poStderrToOutPut, как видно в настройках для второго процесса. | |
− | <syntaxhighlight>program Project1; | + | <syntaxhighlight lang=pascal>program Project1; |
uses | uses | ||
Line 490: | Line 552: | ||
SecondProcess.Executable := 'grep'; | SecondProcess.Executable := 'grep'; | ||
SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); | SecondProcess.Parameters.Add(DirectorySeparator+ ' -'); | ||
− | // | + | // это было бы аналогично "pwd | grep / -" |
FirstProcess.Execute; | FirstProcess.Execute; | ||
Line 499: | Line 561: | ||
if FirstProcess.Output.NumBytesAvailable > 0 then | if FirstProcess.Output.NumBytesAvailable > 0 then | ||
begin | begin | ||
− | // | + | // убеждаемся, что мы не читаем больше данных, чем мы выделили |
− | // | + | // для этого места в буфере |
ReadSize := FirstProcess.Output.NumBytesAvailable; | ReadSize := FirstProcess.Output.NumBytesAvailable; | ||
if ReadSize > SizeOf(Buffer) then | if ReadSize > SizeOf(Buffer) then | ||
ReadSize := SizeOf(Buffer); | ReadSize := SizeOf(Buffer); | ||
− | // | + | // теперь считываем вывод в буфер |
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize); | ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize); | ||
− | // | + | // и пишем буфер во второй процесс |
SecondProcess.Input.Write(Buffer[0], ReadCount); | SecondProcess.Input.Write(Buffer[0], ReadCount); | ||
− | // | + | // если SecondProcess записывает много данных в свой Output, то |
− | // | + | // мы должны прочитать эти данные здесь, чтобы предотвратить блокировку |
− | // | + | // см. предыдущий пример «Чтение больших объемов вывода» |
end; | end; | ||
end; | end; | ||
− | // | + | // Закрываем вход на SecondProcess, |
− | // | + | // поэтому он заканчивает обработку своих данных |
SecondProcess.CloseInput; | SecondProcess.CloseInput; | ||
− | // | + | // и дожидаемся его завершения |
− | // | + | // будьте осторожны с тем, какую команду вы запускаете, потому что она может не завершиться, |
− | // | + | // когда ее вход закроется, следующая строка может зациклиться навечно |
while SecondProcess.Running do | while SecondProcess.Running do | ||
Sleep(1); | Sleep(1); | ||
− | // | + | // вот и все! остальная часть программы просто в качестве примера |
− | // | + | // это немного «полезно» |
− | // | + | // мы будем повторно использовать буфер для вывода SecondProcess'а |
− | // | + | // вывод в *эту* программу stdout |
WriteLn('Grep output Start:'); | WriteLn('Grep output Start:'); | ||
ReadSize := SecondProcess.Output.NumBytesAvailable; | ReadSize := SecondProcess.Output.NumBytesAvailable; | ||
Line 541: | Line 603: | ||
WriteLn('Grep output Finish:'); | WriteLn('Grep output Finish:'); | ||
− | // | + | // освобождаем объекты нашего процесса |
FirstProcess.Free; | FirstProcess.Free; | ||
SecondProcess.Free; | SecondProcess.Free; | ||
end.</syntaxhighlight> | end.</syntaxhighlight> | ||
− | + | Вот и все. Теперь вы можете перенаправлять вывод из одной программы в другую. | |
− | ==== | + | ==== Заметки ==== |
− | + | Этот пример может показаться излишним, поскольку можно запускать «сложные» команды, используя оболочку с TProcess, например: | |
− | <syntaxhighlight>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight> | + | <syntaxhighlight lang=pascal>Process.Commandline := 'sh -c "pwd | grep / -"';</syntaxhighlight> |
− | + | Но наш пример более кроссплатформенный, так как он не нуждается в модификации для работы в Windows или Linux и т.д. "sh" может существовать или не существовать на вашей платформе и обычно доступен только на платформах *nix. Кроме того, у нас больше гибкости в нашем примере, так как вы можете читать и записывать из/в ввода, вывода и stderr каждого процесса в отдельности, что может быть очень выгодно для вашего проекта. | |
− | === | + | ===Перенаправление ввода и вывода и запуск под Root=== |
− | + | Распространенная проблема в Unix(macOS) и Linux заключается в том, что вы хотите выполнить какую-либо программу под учетной записью root (или, в более общем случае, под другой учетной записью пользователя). В качестве примера можно привести команду ''ping''. | |
− | + | Если вы можете использовать sudo для этого, вы можете приспособить следующий пример, адаптированный из примера, опубликованного andyman на форуме ([http://lazarus.freepascal.org/index.php/topic,14479.0.html]). Этот пример запускает <code>ls</code> в каталоге <code>/root</code>, но, конечно, может быть адаптирован. | |
− | + | '''Лучший способ''' сделать это - использовать пакет policykit, который должен быть доступен во всех последних версиях Linux. Подробности смотрите в [http://lazarus.freepascal.org/index.php/topic,14479.0.html ветке форума.] | |
− | + | Большие части этого кода похожи на предыдущий пример, но он также показывает, как перенаправить stdout и stderr процесса, вызываемого отдельно, в stdout и stderr нашего собственного кода. | |
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
program rootls; | program rootls; | ||
− | { | + | {Демонстрирует использование TProcess, перенаправляющего stdout/stderr в наш stdout/stderr, |
− | + | вызов sudo в Linux/OS и обеспечивающего ввод в stdin} | |
+ | |||
{$mode objfpc}{$H+} | {$mode objfpc}{$H+} | ||
Line 583: | Line 646: | ||
SudoPassword: string; | SudoPassword: string; | ||
begin | begin | ||
− | WriteLn(' | + | WriteLn('Пожалуйста, введите пароль sudo:'); |
Readln(SudoPassword); | Readln(SudoPassword); | ||
− | ExitCode := -1; // | + | ExitCode := -1; //Начнем с неудачного вывода, посмотрим позже, работает ли это |
− | Proc := TProcess.Create(nil); // | + | Proc := TProcess.Create(nil); //Создаем новый процесс |
try | try | ||
− | Proc.Options := [poUsePipes, poStderrToOutPut]; // | + | Proc.Options := [poUsePipes, poStderrToOutPut]; //Используем pipes для перенаправления программных stdin, stdout, stderr |
− | Proc.CommandLine := 'sudo -S ls /root'; // | + | Proc.CommandLine := 'sudo -S ls /root'; //Запускаем ls /root как root с помощью sudo |
− | // -S | + | // -S заставляет sudo читать пароль из stdin |
− | Proc.Execute; // | + | Proc.Execute; //Запускаем его. sudo теперь, вероятно, попросит пароль |
− | // | + | // пишем пароль в stdin sudo программы: |
SudoPassword := SudoPassword + LineEnding; | SudoPassword := SudoPassword + LineEnding; | ||
Proc.Input.Write(SudoPassword[1], Length(SudoPassword)); | Proc.Input.Write(SudoPassword[1], Length(SudoPassword)); | ||
− | SudoPassword := '%*'; // | + | SudoPassword := '%*'; // короткая строка, надеюсь, это немного зашифрует память; примечание: использование PChars более надежно |
− | SudoPassword := ''; // | + | SudoPassword := ''; // и сделает программу немного более безопасной от слежки?!? |
− | // | + | // основной цикл для чтения вывода из 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 | ||
− | // | + | // читаем stdout и пишем в наш stdout |
while Proc.Output.NumBytesAvailable > 0 do | while Proc.Output.NumBytesAvailable > 0 do | ||
begin | begin | ||
− | ReadCount := Min(512, Proc.Output.NumBytesAvailable); // | + | 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; | ||
− | // | + | // читаем stderr и пишем в наш stderr |
while Proc.Stderr.NumBytesAvailable > 0 do | while Proc.Stderr.NumBytesAvailable > 0 do | ||
begin | begin | ||
− | ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); // | + | 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 630: | Line 693: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | Другие мысли: | |
− | + | Без сомнения, было бы целесообразно проверить, запрашивает ли sudo пароль. Это можно последовательно проверить, установив переменную окружения SUDO_PROMPT в ту, которую мы наблюдаем при чтении стандартного вывода TProcess, чтобы избежать проблемы, связанной с тем, что приглашение будет различным для разных локалей. Установка переменной среды приводит к тому, что значения по умолчанию очищаются (наследуются от нашего процесса), поэтому мы должны при необходимости скопировать окружение из нашей программы. | |
− | === | + | === Использование fdisk с sudo в Linux === |
− | + | В следующем примере показано, как запустить fdisk на компьютере с Linux с помощью команды sudo для получения прав root. | |
+ | {{Note| это только пример, и он не рассчитан на большой результат}} | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
program getpartitioninfo; | program getpartitioninfo; | ||
− | { | + | |
− | + | {Первоначально пример предоставлен пользователем wjackson153 с форума Lazarus. Пожалуйста, свяжитесь с ним в случае возникновения вопросов, замечаний и т.д. Пример преобразован для простоты понимания/краткости из фрагмента кода Lazarus в программу FPC пользователем BigChimp} | |
Uses | Uses | ||
Line 650: | Line 714: | ||
begin | begin | ||
− | sPass := 'yoursudopasswordhere'; // | + | sPass := 'yoursudopasswordhere'; // Вы должны изменить это на свой собственный пароль sudo |
− | OutputLines:=TStringList.Create; //... | + | OutputLines:=TStringList.Create; //... было бы неплохо убедиться с помощью блока try..finally, |
− | // OutputLines | + | // что OutputLines освобожден ... То же самое для hProcess. |
− | // | + | // Следующий пример откроет fdisk в фоновом режиме и даст нам информацию о разделе |
− | // | + | // Поскольку fdisk требует повышенных привилегий, нам нужно |
− | // | + | // передать наш пароль в качестве параметра sudo с помощью параметра -S, |
− | // | + | // поэтому он будет ждать, пока наша программа отправит наш пароль в sudo приложение |
hProcess := TProcess.Create(nil); | hProcess := TProcess.Create(nil); | ||
− | // | + | // В Linux/Unix/macOS нам нужно указать полный путь к нашему исполняемому файлу: |
hProcess.Executable := '/bin/sh'; | hProcess.Executable := '/bin/sh'; | ||
− | // | + | // Теперь мы добавляем все параметры в командную строку: |
hprocess.Parameters.Add('-c'); | hprocess.Parameters.Add('-c'); | ||
− | // | + | // Здесь мы передаем пароль в команду sudo, которая затем выполняет fdisk -l: |
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l'); | hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l'); | ||
− | // | + | // Запускаем асинхронно (дожидаемся завершения процесса) и используем pipes, чтобы мы могли прочитать output pipe |
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes]; | hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes]; | ||
− | // | + | // Теперь запускаем: |
hProcess.Execute; | hProcess.Execute; | ||
− | // hProcess | + | // hProcess должен запустить внешний исполняемый файл (потому что мы используем poWaitOnExit). |
− | // | + | // Теперь вы можете обработать 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); | ||
− | // | + | // Показываем output на экране: |
writeln(OutputLines.Text); | writeln(OutputLines.Text); | ||
− | // | + | // Очищаем, чтобы избежать утечек памяти: |
hProcess.Free; | hProcess.Free; | ||
OutputLines.Free; | OutputLines.Free; | ||
− | // | + | //Ниже приведены некоторые примеры, как вы видите, мы можем передавать недопустимые символы, как если бы это было сделано из терминала |
− | // | + | //Даже, если вы где-то читали, что это невозможно, я уверяю вас, что при помощи этого метода вы сможете :) |
//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'); | ||
− | // | + | //Использование QuotedStr() не является обязательным требованием, хотя делает его более читабельным кодом; |
− | // | + | //Вы можете использовать двойные кавычки и добиться того же эффекта |
//hprocess.Parameters.Add('glxinfo | grep direct'); | //hprocess.Parameters.Add('glxinfo | grep direct'); | ||
− | // | + | // Этот метод также можно использовать для установки приложений из вашего хранилища: |
//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 701: | Line 765: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | === | + | ===Параметры, содержащие пробелы (замена кавычек оболочки Shell)=== |
− | + | В оболочке Linux можно записать в кавычки следующие аргументы: | |
− | + | <syntaxhighlight lang=bash>gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram</syntaxhighlight> | |
− | + | И GDB получит 3 аргумента (в дополнение к первому аргументу, который является полным путем к исполняемому файлу): | |
#--batch | #--batch | ||
#--eval-command=info symbol 0x0000DDDD | #--eval-command=info symbol 0x0000DDDD | ||
− | # | + | #полный путь к myprogram |
− | TProcess | + | TProcess также может передавать параметры, содержащие пробелы, но использует другой стиль квотирования. Вместо того, чтобы заключать в кавычки только часть параметра, заключите в кавычки все из них. Вот так: |
− | + | <syntaxhighlight lang=bash>TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';</syntaxhighlight> | |
− | + | И также не забудьте передать только полный путь. | |
− | + | Смотрите также эту дискуссию об этом: [http://bugs.freepascal.org/view.php?id=14446 здесь] | |
==Альтернативные решения с помощью LCLIntf== | ==Альтернативные решения с помощью LCLIntf== | ||
− | Иногда вам | + | Иногда вам не нужно явно вызывать внешнюю программу, чтобы получить необходимую вам функциональность. Вместо того, чтобы открывать приложение и указывать документ для него, просто попросите ОС открыть документ, позволив ей использовать приложение по умолчанию, связанное с этим типом файла. Ниже приведены некоторые примеры. |
− | === | + | ===Открытие документа в приложении по умолчанию=== |
− | В некоторых ситуациях вам | + | В некоторых ситуациях вам нужно открыть какой-либо документ/файл с использованием приложения по умолчанию, а не выполнить определенную программу. Это зависит от запущенной операционной системы. Lazarus предоставляет платформ-независимую процедуру '''OpenDocument''', которая будет делать это за вас. Ваше приложение продолжит работу, не дожидаясь закрытия процесса документа. |
− | Lazarus предоставляет | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
uses LCLIntf; | uses LCLIntf; | ||
... | ... | ||
Line 737: | Line 800: | ||
* [[opendocument|справка по OpenDocument]] | * [[opendocument|справка по OpenDocument]] | ||
− | === | + | ===Открытие web-страницы в web-браузере по умолчанию=== |
+ | |||
+ | Просто передайте требуемый URL, ''http://'' в начале строки является необязательным при определенных условиях. Кроме того, передача имени файла дает те же результаты, что и OpenDocument() | ||
− | + | <syntaxhighlight lang=pascal> | |
− | <syntaxhighlight> | ||
uses LCLIntf; | uses LCLIntf; | ||
... | ... | ||
Line 747: | Line 811: | ||
См. также: | См. также: | ||
− | * [[ | + | * [[OpenURL|справка по OpenURL]] |
Кроме того, вы можете использовать '''TProcess''' как в данном примере: | Кроме того, вы можете использовать '''TProcess''' как в данном примере: | ||
− | <syntaxhighlight> | + | <syntaxhighlight lang=pascal> |
uses Process; | uses Process; | ||
procedure OpenWebPage(URL: string); | procedure OpenWebPage(URL: string); | ||
− | // | + | // Очевидно, вам нужно передать свой URL внутри кавычек "", например, "www.lazarus.freepascal.org" |
var | var | ||
Browser, Params: string; | Browser, Params: string; | ||
Line 763: | Line 827: | ||
Executable := Browser; | Executable := Browser; | ||
Params:=Format(Params, [URL]); | Params:=Format(Params, [URL]); | ||
− | Params:=copy(Params,2,length(Params)-2); // | + | Params:=copy(Params,2,length(Params)-2); // удаляем "", новая версия TProcess.Parameters делает это сама |
Parameters.Add(Params); | Parameters.Add(Params); | ||
Options := [poNoConsole]; | Options := [poNoConsole]; | ||
Line 775: | 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]] |
* [[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-ресурсе | |
− | [ | ||
− | |||
− | |||
− | |||
− | |||
− |
Latest revision as of 16:22, 19 June 2023
│
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 является кроссплатформенным компонентом).
(Process.)RunCommand
- RunCommand документация
- RunCommandInDir докуметация
В 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
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.
Важно: Вы должны определять полный путь к исполняемому файлу. Например '/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 соответственно.
...
// к примеру, один из аргументов командной строки должен быть '-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.
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 аргумента (в дополнение к первому аргументу, который является полным путем к исполняемому файлу):
- --batch
- --eval-command=info symbol 0x0000DDDD
- полный путь к 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;
См. также
- Документация по TProcess
- OpenDocument
- OpenURL
- TProcessUTF8
- TXMLPropStorage
- Webbrowser
- Запуск чужой программы из своей - статья на русскоязычном freepascal-ресурсе