Sign in Join
Wiki copytemporary

Executing External Programs/ru: what changed

Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.

Title: Executing External Programs/ru

OldNewLine
1+## Введение: сравнение
2+
3+В библиотеках RTL, FCL, LCL есть разные способы выполнить внешнюю программу.
4+
5+| Метод | Библиотека | Платформы | Одной строкой? | Особенности |
6+|----|----|----|----|----|
7+| [[Executing External Programs/ru|ExecuteProcess]] | RTL | кроссплатформенный | Да | Очень ограничен, синхронен |
8+| [[Executing External Programs/ru|ShellExecute]] | WinAPI | Только MS Windows | Да | Много. Может запускать программы с правами администратора |
9+| [[Executing External Programs/ru|fpsystem, fpexecve]] | Unix | Unix only | | |
10+| [[Executing External Programs/ru|TProcess]] | FCL | кроссплатформенный | Нет | Все |
11+| [[Executing External Programs/ru|RunCommand]] | FCL | кроссплатформенный **Требуется FPC 2.6.2+** | Да | Покрывает стандартное использование TProcess |
12+| [[Executing External Programs/ru|OpenDocument]] | LCL | кроссплатформенный | Да | Только открывает документ. Документ будет открыт стандартной программой |
13+
14+Если вы использовали **ShellExecute** и/или **WinExec** в Delphi, то вы можете начать использовать TProcess как альтернативу в FPC/Lazarus (это верно и в случае использования Lazarus на Linux, потому что TProcess является кроссплатформенным компонентом).
15+
16+> **Note:** FPC/Lazarus поддерживает **ShellExecute** и **WinExec**, но только в среде Win32. Если вы пишете кросс-платформенную программу, то лучшим путем будет использование TProcess!
17+
18+## (Process.)RunCommand
19+
20+- [RunCommand](http://www.freepascal.org/docs-html/fcl/process/runcommand.html) документация
21+- [RunCommandInDir](http://www.freepascal.org/docs-html/fcl/process/runcommandindir.html) докуметация
22+
23+В FPC 2.6.2, некоторые вспомогательные функции для TProcess были добавлены в модуль process основанный на обертке использованной в [[Projects using Lazarus|fpcup]]. Эти функции могут быть для базового и среднего уровня использования и могот захватить вывод как одной строки, так и *огромного количества* выводимой информации.
24+
25+Простой пример:
26+
27+``` pascal
28+uses Process;
29+...
30+var s : ansistring;
31+...
32+if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
33+ writeln(s);
34+```
35+
36+Перегруженный вариант функции может возвращать код выхода программы. RunCommandInDir запускает программу в заданной папке.
37+
38+``` pascal
39+function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string; var exitstatus:integer): integer;
40+function RunCommandIndir(const curdir:string;const exename:string;const commands:array of string;var outputstring:string): boolean;
41+function RunCommand(const exename:string;const commands:array of string;var outputstring:string): boolean;
42+```
43+
44+## SysUtils.ExecuteProcess
45+
46+Простейший путь, если вам не нужно общение с процессом - это просто использовать вызов
47+
48+``` pascal
49+ SysUtils.ExecuteProcess('/full/path/to/binary',['arg1','arg2']);
50+```
51+
52+При использовании этого метода, приложение "повисает" до тех пор, пока вызванная программа не завершится. Это может быть полезно если пользователь должен сделать что-то перед использованием вашего приложения. Если вы хотите обойти это ограничение, то более полезный кроссплатформенный способ - это **TProcess**, или если ваша целевая платформа только Windows, то используйте **ShellExecute**.
53+
54+## MS Windows : CreateProcess, ShellExecute и WinExec
55+
56+> **Note:** FPC/Lazarus поддерживают **CreateProcess**, **ShellExecute** и/или **WinExec**, только на Win32/64. Если ваша программа кроссплатформенная, используйте **RunCommand** или **TProcess**.
57+
58+> **Note:** WinExec использует 16-битные вызовы и давно устарел. Новые версии FPC при его использовании генерируют предупреждение.
59+
60+**ShellExecute** - стандартная функция MS Windows (ShellApi.h) с хорошей \[<http://msdn.microsoft.com/en-us/library/windows/desktop/bb762153(v=vs.85>).aspx документацией на MSDN\] (читайте документацию, если вам кажется, что функция ненадежна).
61+
62+``` pascal
63+uses ..., ShellApi;
64+
65+// Обычный текст (возвращаемые ошибки игнорируются) :
66+if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) = 0 then;
67+
68+// Выполняем Batch файл:
69+if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) = 0 then;
70+
71+// Открываем командную строку в заданной папке:
72+if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) = 0 then;
73+
74+// Открываем веб-страницу стандартным браузером с помощью'start' (через скрытую командную строку) :
75+if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;
76+
77+// или полезную команду:
78+procedure RunShellExecute(const prog,params:string);
79+begin
80+ // ( Handle, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' не всегда требуется
81+ // path+prog, params, working folder,
82+ // 0=hide / 1=SW_SHOWNORMAL / 3=max / 7=min) // for SW_ constants : uses ... Windows ...
83+ if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //успех
84+ // если возвращается число в диапазоне 0..32, то значит ошибка
85+end;
86+```
87+
88+Также есть WideChar функции - ShellExecuteExW , ShellExecuteExA в режиме AnsiChar.
89+
90+В качестве fMask можно ипользовать SEE_MASK_DOENVSUBST, SEE_MASK_FLAG_NO_UI или SEE_MASK_NOCLOSEPROCESS и тд Если в Delphi вы используете ShellExecute для открытия документов, например документ Word или ссылки, то присмотритесь к open* (OpenURL и т.д.) модуле lclintf.
91+
92+### Использование ShellExecuteEx с повышенными правами (администратора)
93+
94+Если вы хотите использовать программу с повышенными (правами администратора) правами, используйте **runas** как альтернативу ShellExecuteEx:
95+
96+``` pascal
97+uses ShellApi, ...;
98+
99+function RunAsAdmin(const Handle: Hwnd; const Path, Params: string): Boolean;
100+var
101+ sei: TShellExecuteInfoA;
102+begin
103+ FillChar(sei, SizeOf(sei), 0);
104+ sei.cbSize := SizeOf(sei);
105+ sei.Wnd := Handle;
106+ sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI;
107+ sei.lpVerb := 'runas';
108+ sei.lpFile := PAnsiChar(Path);
109+ sei.lpParameters := PAnsiChar(Params);
110+ sei.nShow := SW_SHOWNORMAL;
111+ Result := ShellExecuteExA(@sei);
112+end;
113+
114+procedure TFormMain.RunAddOrRemoveApplication;
115+begin
116+ // Пример, открывающий панель управления с повышенными правами
117+ RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');
118+end;
119+```
120+
121+## Unix fpsystem, fpexecve и shell
122+
123+Эти функции являются платформозависимыми.
124+
125+- [справка по fpsystem](http://www.freepascal.org/docs-html/rtl/unix/fpsystem.html)
126+- [справка по fpexecve](http://www.freepascal.org/docs-html/rtl/baseunix/fpexecve.html)
127+- [справка по shell](http://www.freepascal.org/docs-html/rtl/unix/shell.html)
128+
129+Учтите, что **Unix.Shell** версии 1.0.x устарел и удален из trunk. Используйте fpsystem.
130+
131+## TProcess
132+
133+Вы можете использовать TProcess для запуска внешних программ. Самыми полезными вещами при этом будут:
134+
135+- Платформонезависимость
136+- Способность читать из stdout и писать в stdin.
137+
138+> **Note:** TProcess не оболочка! И не терминал! Вы не можете напрямую исполнять скрипты или перенаправлять вывод используя такие операторы, как "
139+
140+Важно: Вы должны определять полный путь к исполняемому файлу. Например '/bin/cp' вместо 'cp'. Если программа находится где-либо в переменной PATH, то вы можете использовать функцию [FindDefaultExecutablePath](https://lazarus-ccr.sourceforge.io/docs/lazutils/fileutil/finddefaultexecutablepath.html) из модуля LCL [FileUtil](https://lazarus-ccr.sourceforge.io/docs/lazutils/fileutil/index.html).
141+
142+### Простейший пример
143+
144+Многие типичные случаи были подготовлены в функциях [[Executing External Programs|Runcommand]]. Прежде, чем начать копировать и вставлять приведенные ниже примеры, сначала проверьте их.
145+
146+### Простой пример
147+
148+``` pascal
149+ // Демо-программа, показывающая, как можно запустить
150+ // внешнюю программу
151+ program launchprogram;
152+
153+ // Подключаем модули с требуемыми
154+ // нам процедурами и функциями.
155+ uses
156+ Classes, SysUtils, Process;
157+
158+ // Опишем переменную "AProcess"
159+ // типа "TProcess"
160+ var
161+ AProcess: TProcess;
162+
163+ // Здесь наша программа начинается
164+ begin
165+ // Создаем объект TProcess и
166+ // присваиваем его переменной AProcess.
167+ AProcess := TProcess.Create(nil);
168+
169+ // Сообщаем новому AProcess, что это за команда для выполнения.
170+   // Давайте использовать компилятор Free Pascal (версия i386)
171+ AProcess.Executable:= 'ppc386';
172+
173+ // Передаеv -h вместе с ppc386, чтобы фактически выполнить 'ppc386 -h':
174+ AProcess.Parameters.Add('-h');
175+
176+ // Мы определим опцию, когда программа
177+ // будет запущена. Эта опция гарантирует, что наша программа
178+ // не продолжит работу, пока программа, которую мы запустили,
179+ // не перестанет работать. vvvvvvvvvvvvvv
180+ AProcess.Options := AProcess.Options + [poWaitOnExit];
181+
182+ // Теперь пусть Process запустит программу
183+ AProcess.Execute;
184+
185+ // Пока ppc386 не прекратит работу, мы досюда не дойдем
186+ AProcess.Free;
187+ end.
188+```
189+
190+Вот оно! Теперь вы научились запускать внешнюю программу изнутри вашей собственной.
191+
192+------------------------------------------------------------------------
193+
194+Прим.перев.: на момент правки статьи (fpc 3.0.4) свойство *CommandLine* (как и *ApplicationName*) согласно [документации](https://www.freepascal.org/docs-html/current/fcl/process/tprocess.html) объявлены **deprecated**. Вместо них рекомендуется использовать свойства [Parameters](https://www.freepascal.org/docs-html/current/fcl/process/tprocess.parameters.html) и [Executable](https://www.freepascal.org/docs-html/current/fcl/process/tprocess.executable.html) соответственно.
195+
196+> **Note:** для пользователей Windows - если путь к исполняемому файлу включает символы пробела, табуляции или перевода строки, то он должен заключаться в двойные кавычки (например, "c:\Program Files\Git\bin\git.exe"). Это же правило справедливо в случае, если такой путь указывается в аргументах командной строки (в свойстве Parameters).
197+
198+> **Note:** строковый агрумент, добавляемый в Parameters, не должен содержать пробелы.
199+
200+``` pascal
201+...
202+// к примеру, один из аргументов командной строки должен быть '-user sysdba'
203+//тогда он должен быть записан так
204+AProcess.Parameters.Add('-user');
205+AProcess.Parameters.Add('sysdba');
206+...
207+```
208+
209+------------------------------------------------------------------------
210+
211+### Усовершенствованный пример (но пока не правильный)
212+
213+Это все замечательно, но как я могу получить вывод программы, которую я запустил? Что ж, давайте немного расширим наш пример и сделаем так: **этот пример прост, поэтому вы можете извлечь из него уроки. Пожалуйста, не используйте этот пример в рабочем коде, а используйте код из раздела** [[Executing External Programs/ru|Чтение больших объемов вывода]]
214+
215+``` pascal
216+// Это
217+// ДЕФЕКТНАЯ
218+// демонстрационная программа, которая показывает
219+// как запустить внешнюю программу
220+// и читать из ее вывода.
221+program launchprogram;
222+
223+// Здесь мы включаем файлы, которые имеют полезные функции
224+// и процедуры, и которые нам понадобятся.
225+uses
226+ Classes, SysUtils, Process;
227+
228+// Это определение переменной «AProcess», как переменной
229+// типа "TProcess".
230+// Также еще мы добавляем TStringList для хранения
231+// данных, читаемых из вывода программ.
232+var
233+ AProcess: TProcess;
234+ AStringList: TStringList;
235+
236+// Это место, где наша программа начинает работать
237+begin
238+ // Теперь мы создадим объект Process и
239+ // назначим его переменной var AProcess.
240+ AProcess := TProcess.Create(nil);
241+
242+ // Сообщим новому AProcess, что это за команда для выполнения.
243+ AProcess.Executable := '/usr/bin/ppc386';
244+ AProcess.Parameters.Add('-h');
245+
246+ // Мы определим опцию, когда программа
247+  // выполняется. Эта опция гарантирует, что наш AProcess
248+  // не будет продолжаться до тех пор, пока запущенная нами программа
249+  // остановится. Также теперь мы сообщим AProcess, что
250+  // мы хотим прочитать вывод файла.
251+ AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
252+
253+ // Теперь, когда AProcess знает, что это за командная строка, его можно запустить.
254+ AProcess.Execute;
255+
256+ // После завершения AProcess, остальная часть программы будет выполнена.
257+
258+ // Теперь читаем вывод программы, который мы только что запихали в TStringList.
259+ AStringList := TStringList.Create;
260+ AStringList.LoadFromStream(AProcess.Output);
261+
262+ // Сохраняем выходные данные в файл и очищаем TStringList.
263+ AStringList.SaveToFile('output.txt');
264+ AStringList.Free;
265+
266+ // Теперь, когда выходные данные процесса обработаны, его можно освободить.
267+ AProcess.Free;
268+end.
269+```
270+
271+### Чтение больших объемов вывода
272+
273+В предыдущем примере мы ждали завершения запущенной программы. После этого мы считывали все, что программа записала в выходной поток. Но ведь может оказаться и так, что программа выведет много данных, канал заполнится и вывод остановится, при этом запустившая программа ждет завершения запущенной программы, которая в свою очередь не может завершить работу, пока не выведет все данные. Возникает коллизия, dead-lock.
274+
275+Поэтому следующий пример не будет использовать опцию poWaitOnExit и будет читать данные при запущенной программе. Вывод записывается в поток, который можно позже использовать для чтения его содержимого в TStringList.
276+
277+Если вы хотите считывать выходные данные из внешнего процесса, вы должны адаптировать этот код для повседневного использования.
278+
279+``` pascal
280+ program LargeOutputDemo;
281+
282+{$mode objfpc}{$H+}
283+
284+ uses
285+ Classes, Process, SysUtils; // Process - модуль, содержащий TProcess
286+
287+const
288+ BUF_SIZE = 2048; // Размер буфера для чтения выходных данных блоками
289+var
290+ AProcess : TProcess;
291+ OutputStream : TStream;
292+ BytesRead : longint;
293+ Buffer : array[1..BUF_SIZE] of byte;
294+
295+begin
296+ // Установки процесса; в качестве примера используется рекурсивный поиск в каталоге,
297+  // потому что это обычно приводит к большому количеству данных.
298+ AProcess := TProcess.Create(nil);
299+
300+ //Команды для Windows и *nix отличаются, поэтому используется директива $IFDEFs
301+ {$IFDEF Windows}
302+ // В Windows команду dir нельзя использовать напрямую, потому что она является встроенной
303+    // командой оболочки. Поэтому нужны cmd.exe и дополнительные параметры
304+ AProcess.Executable := 'c:\windows\system32\cmd.exe';
305+ AProcess.Parameters.Add('/c');
306+ AProcess.Parameters.Add('dir /s c:\windows');
307+ {$ENDIF Windows}
308+
309+ {$IFDEF Unix}
310+ AProcess.Executable := '/bin/ls';
311+ AProcess.Parameters.Add('--recursive');
312+ AProcess.Parameters.Add('--all');
313+ AProcess.Parameters.Add('-l');
314+ {$ENDIF Unix}
315+
316+ // Необходимо использовать poUsePipes - параметр TProcess, чтобы можно было получить выходные данные.
317+  // Параметр TProcess'а poWaitOnExit не может быть использован, потому что он заблокирует
318+  // эту программу до окончания вызванного процесса, препятствуя чтению выходных данных процесса.
319+ AProcess.Options := [poUsePipes];
320+
321+ //Запуск процесса (выполнение команды dir/ls)
322+ AProcess.Execute;
323+
324+ // Создаем объект потока для хранения сгенерированного вывода. Вывод может
325+  // также являться файловым потоком для непосредственного сохранения выводимых данных на диск.
326+ OutputStream := TMemoryStream.Create;
327+
328+ // Все сгенерированные выходные данные из AProcess читаются в цикле, пока больше не останется данных
329+ repeat
330+ // Получаем новые данные из процесса до максимального размера выделенного буфера.
331+    // Обратите внимание, что все вызовы read (...) будут блокироваться, кроме последнего, который возвращает 0 (ноль).
332+ BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
333+
334+ // Добавляем байты, которые были прочитаны в поток для последующего использования
335+ OutputStream.Write(Buffer, BytesRead)
336+
337+ until BytesRead = 0; // Останавливаемся, если больше нет данных
338+
339+ // Процесс завершен, поэтому его можно очистить
340+ AProcess.Free;
341+
342+ //Теперь, когда все данные прочитаны, их можно использовать; например, чтобы сохранить их в файл на диске
343+ with TFileStream.Create('output.txt', fmCreate) do
344+ begin
345+ OutputStream.Position := 0; // Требуется, чтобы убедиться, что все данные копируются с самого начала
346+ CopyFrom(OutputStream, OutputStream.Size);
347+ Free
348+ end;
349+
350+ // Или данные могут быть показаны на экране
351+ with TStringList.Create do
352+ begin
353+ OutputStream.Position := 0; // Требуется, чтобы убедиться, что все данные копируются с самого начала
354+ LoadFromStream(OutputStream);
355+ writeln(Text);
356+ writeln('--- Номер строки = ', Count, '----');
357+ Free
358+ end;
359+
360+ // Очищаем
361+ OutputStream.Free;
362+end.
363+```
364+
365+------------------------------------------------------------------------
366+
367+Прим.перев.: можно выводить данные сразу в какой-нибудь memoOutput на основе ([решения](https://forum.lazarus.freepascal.org/index.php/topic,17315.msg95228.html#msg95228), предложенного пользователем [KpjComp](https://forum.lazarus.freepascal.org/index.php?action=profile;u=47822)):
368+
369+``` pascal
370+const
371+ BUF_SIZE = 1024 * 64; // Размер буфера для чтения выходных данных, можно читать блоками по 64k
372+var
373+ Buffer: string = '';
374+ BytesRead: LongInt = 0;
375+begin
376+...
377+ //Запуск процесса (выполнение команды dir/ls)
378+ AProcess.Execute;
379+
380+ //можно дать чуть-чуть времени на отправку данных через AProcess.Output
381+ sleep(500);
382+
383+ repeat
384+ memoOutput.Lines.BeginUpdate;
385+ try
386+ SetLength(Buffer,BUF_SIZE);
387+ BytesRead := AProcess.Output.Read(Buffer[1],Length(Buffer));
388+ if BytesRead > 0 then
389+ begin
390+ SetLength(Buffer,BytesRead);
391+ memoOutput.Append(Buffer);
392+ end;
393+ finally
394+ memoOutput.Lines.EndUpdate;
395+ memoOutput.SelStart := UTF8Length(memoOutput.Text);
396+ end;
397+ Sleep(50);
398+ Application.ProcessMessages;
399+ until BytesRead = 0;
400+```
401+
402+------------------------------------------------------------------------
403+
404+Обратите внимание, что вышеперечисленное также может быть достигнуто с использованием RunCommand:
405+
406+``` pascal
407+var s: string;
408+...
409+RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);
410+```
411+
412+### Использование ввода и вывода TProcess
413+
414+Смотри демо пример на [Lazarus-CCR SVN](https://sourceforge.net/p/lazarus-ccr/svn/913/tree/examples/process).
415+
416+### Некоторые подсказки при использовании TProcess
417+
418+Если вы создаете кроссплатформенную программу, вы можете изменять командную строку применительно к каждой ОС использую директивы "{\$IFDEF}" и "{\$ENDIF}".
419+
420+Например:
421+
422+``` pascal
423+ {...}
424+ AProcess:TProcess.Create(nil)
425+ {$IFDEF WIN32}
426+ AProcess.CommandLine := 'calc.exe'; // Windows калькулятор
427+ {$ENDIF}
428+ {$IFDEF LINUX}
429+ AProcess.CommandLine := 'kcalc'; // KDE калькулятор
430+ {$ENDIF}
431+ AProcess.Execute; // как альтернативу, вы можете использовать AProcess.Active:=True
432+ {...}
433+```
434+
435+### Показ комплекта приложений на переднем плане в macOS
436+
437+Вы можете запустить **application bundle**(пакет приложения) через TProcess, вызвав исполняемый файл внутри пакета. Например:
438+
439+``` pascal
440+ AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';
441+```
442+
443+Это запустит *Calendar*, но окно будет позади текущего приложения. Чтобы получить приложение на переднем плане, вы можете использовать утилиту **open** с параметром **-n**:
444+
445+``` pascal
446+ AProcess.Executable:='/usr/bin/open';
447+ AProcess.Parameters.Add('-n');
448+ AProcess.Parameters.Add('-a'); //необязательно: скрыть терминал - аналогично опции Windows poNoConsole
449+ AProcess.Parameters.Add('/Application/iCal.app');
450+```
451+
452+Если вашему приложению нужны параметры, вы можете передать **open** параметр **--args**, после чего все параметры будут переданы приложению:
453+
454+``` pascal
455+ AProcess.Parameters.Add('--args');
456+ AProcess.Parameters.Add('argument1');
457+ AProcess.Parameters.Add('argument2');
458+```
459+
460+### Запуск отдельной программы
461+
462+Обычно программа, запускаемая вашим приложением, является дочерним процессом и уничтожается, когда ваше приложение уничтожается. Если вы хотите запустить автономную программу, которая продолжает работать \[после завершения вашего приложения\], вы можете использовать следующее:
463+
464+``` pascal
465+var
466+ Process: TProcess;
467+ I: Integer;
468+begin
469+ Process := TProcess.Create(nil);
470+ try
471+ Process.InheritHandles := False;
472+ Process.Options := [];
473+ Process.ShowWindow := swoShow;
474+
475+ // Копируем переменные среды по умолчанию, включая переменную DISPLAY, для работы приложения с графическим интерфейсом
476+ for I := 1 to GetEnvironmentVariableCount do
477+ Process.Environment.Add(GetEnvironmentString(I));
478+
479+ Process.Executable := '/usr/bin/gedit';
480+ Process.Execute;
481+ finally
482+ Process.Free;
483+ end;
484+end;
485+```
486+
487+### Пример "общения" с процессом aspell
488+
489+Внутри исходного кода [pasdoc](https://github.com/pasdoc/pasdoc/wiki) вы можете найти два модуля, которые выполняют проверку орфографии, "общаясь" с запущенным процессом aspell через каналы:
490+
491+- Модуль [PasDoc_ProcessLineTalk.pas](https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_ProcessLineTalk.pas) реализует класс TProcessLineTalk, потомок TProcess, который может быть легко использован для общения с любым процессом построчно.
492+
493+- Модуль [PasDoc_Aspell.pas](https://github.com/pasdoc/pasdoc/blob/master/source/component/PasDoc_Aspell.pas) реализует класс TAspellProcess, который выполняет проверку орфографии с использованием базового экземпляра TProcessLineTalk для выполнения aspell и связи с запущенным процессом aspell.
494+
495+Оба модуля довольно независимы от остальных исходников pasdoc, поэтому они могут служить реальными примерами использования TProcess для запуска и связи по каналам с другой программой.
496+
497+### Замена операторов командной оболочки, таких как "\| \< \>"
498+
499+Иногда у вас есть желание выполнить более сложную команду, которая передает свои данные другой команде или файлу. Что-то вроде
500+
501+``` bash
502+ShellExecute('firstcommand.exe | secondcommand.exe');
503+```
504+
505+или
506+
507+``` bash
508+ShellExecute('dir > output.txt');
509+```
510+
511+Запуск таких команд с TProcess не будет работать. То есть:
512+
513+``` pascal
514+// так работать не будет
515+Process.CommandLine := 'firstcommand.exe | secondcommand.exe';
516+Process.Execute;
517+```
518+
519+#### Почему использование специальных операторов для перенаправления вывода не работает
520+
521+TProcess - это не оболочка, а процесс. Это не два процесса, а всего один. Можно перенаправить вывод так, как вы этого пожелаете. См. [[Executing External Programs/ru|следующий раздел]].
522+
523+### Как перенаправить вывод с помощью TProcess
524+
525+Вы можете перенаправлять вывод команды другой команде, используя экземпляр TProcess для **каждой** команды.
526+
527+Вот пример, который объясняет, как перенаправить вывод одного процесса другому. Чтобы перенаправить вывод процесса в файл/поток, см. пример [[Executing External Programs/ru|чтение больших объемов вывода]].
528+
529+Вы можете не только перенаправлять «обычный» вывод (также известный как stdout), но также можете перенаправить вывод ошибок (stderr), если вы укажете опцию poStderrToOutPut, как видно в настройках для второго процесса.
530+
531+``` pascal
532+program Project1;
533+
534+uses
535+ Classes, sysutils, process;
536+
537+var
538+ FirstProcess,
539+ SecondProcess: TProcess;
540+ Buffer: array[0..127] of char;
541+ ReadCount: Integer;
542+ ReadSize: Integer;
543+begin
544+ FirstProcess := TProcess.Create(nil);
545+ SecondProcess := TProcess.Create(nil);
546+
547+ FirstProcess.Options := [poUsePipes];
548+ FirstProcess.Executable := 'pwd';
549+
550+ SecondProcess.Options := [poUsePipes,poStderrToOutPut];
551+ SecondProcess.Executable := 'grep';
552+ SecondProcess.Parameters.Add(DirectorySeparator+ ' -');
553+ // это было бы аналогично "pwd | grep / -"
554+
555+ FirstProcess.Execute;
556+ SecondProcess.Execute;
557+
558+ while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
559+ begin
560+ if FirstProcess.Output.NumBytesAvailable > 0 then
561+ begin
562+ // убеждаемся, что мы не читаем больше данных, чем мы выделили
563+ // для этого места в буфере
564+ ReadSize := FirstProcess.Output.NumBytesAvailable;
565+ if ReadSize > SizeOf(Buffer) then
566+ ReadSize := SizeOf(Buffer);
567+ // теперь считываем вывод в буфер
568+ ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
569+ // и пишем буфер во второй процесс
570+ SecondProcess.Input.Write(Buffer[0], ReadCount);
571+
572+ // если SecondProcess записывает много данных в свой Output, то
573+ // мы должны прочитать эти данные здесь, чтобы предотвратить блокировку
574+ // см. предыдущий пример «Чтение больших объемов вывода»
575+ end;
576+ end;
577+ // Закрываем вход на SecondProcess,
578+ // поэтому он заканчивает обработку своих данных
579+ SecondProcess.CloseInput;
580+
581+ // и дожидаемся его завершения
582+ // будьте осторожны с тем, какую команду вы запускаете, потому что она может не завершиться,
583+ // когда ее вход закроется, следующая строка может зациклиться навечно
584+ while SecondProcess.Running do
585+ Sleep(1);
586+ // вот и все! остальная часть программы просто в качестве примера
587+ // это немного «полезно»
588+
589+ // мы будем повторно использовать буфер для вывода SecondProcess'а
590+ // вывод в *эту* программу stdout
591+ WriteLn('Grep output Start:');
592+ ReadSize := SecondProcess.Output.NumBytesAvailable;
593+ if ReadSize > SizeOf(Buffer) then
594+ ReadSize := SizeOf(Buffer);
595+ if ReadSize > 0 then
596+ begin
597+ ReadCount := SecondProcess.Output.Read(Buffer, ReadSize);
598+ WriteLn(Copy(Buffer,0, ReadCount));
599+ end
600+ else
601+ WriteLn('grep did not find what we searched for. ', SecondProcess.ExitStatus);
602+ WriteLn('Grep output Finish:');
603+
604+ // освобождаем объекты нашего процесса
605+ FirstProcess.Free;
606+ SecondProcess.Free;
607+end.
608+```
609+
610+Вот и все. Теперь вы можете перенаправлять вывод из одной программы в другую.
611+
612+#### Заметки
613+
614+Этот пример может показаться излишним, поскольку можно запускать «сложные» команды, используя оболочку с TProcess, например:
615+
616+``` pascal
617+Process.Commandline := 'sh -c "pwd | grep / -"';
618+```
619+
620+Но наш пример более кроссплатформенный, так как он не нуждается в модификации для работы в Windows или Linux и т.д. "sh" может существовать или не существовать на вашей платформе и обычно доступен только на платформах *nix. Кроме того, у нас больше гибкости в нашем примере, так как вы можете читать и записывать из/в ввода, вывода и stderr каждого процесса в отдельности, что может быть очень выгодно для вашего проекта.
621+
622+### Перенаправление ввода и вывода и запуск под Root
623+
624+Распространенная проблема в Unix(macOS) и Linux заключается в том, что вы хотите выполнить какую-либо программу под учетной записью root (или, в более общем случае, под другой учетной записью пользователя). В качестве примера можно привести команду *ping*.
625+
626+Если вы можете использовать sudo для этого, вы можете приспособить следующий пример, адаптированный из примера, опубликованного andyman на форуме ([1](http://lazarus.freepascal.org/index.php/topic,14479.0.html)). Этот пример запускает `ls` в каталоге `/root`, но, конечно, может быть адаптирован.
627+
628+**Лучший способ** сделать это - использовать пакет policykit, который должен быть доступен во всех последних версиях Linux. Подробности смотрите в [ветке форума.](http://lazarus.freepascal.org/index.php/topic,14479.0.html)
629+
630+Большие части этого кода похожи на предыдущий пример, но он также показывает, как перенаправить stdout и stderr процесса, вызываемого отдельно, в stdout и stderr нашего собственного кода.
631+
632+``` pascal
633+program rootls;
634+
635+{Демонстрирует использование TProcess, перенаправляющего stdout/stderr в наш stdout/stderr,
636+вызов sudo в Linux/OS и обеспечивающего ввод в stdin}
637+
638+{$mode objfpc}{$H+}
639+
640+uses
641+ Classes,
642+ Math, {for min}
643+ Process;
644+
645+ procedure RunsLsRoot;
646+ var
647+ Proc: TProcess;
648+ CharBuffer: array [0..511] of char;
649+ ReadCount: integer;
650+ ExitCode: integer;
651+ SudoPassword: string;
652+ begin
653+ WriteLn('Пожалуйста, введите пароль sudo:');
654+ Readln(SudoPassword);
655+ ExitCode := -1; //Начнем с неудачного вывода, посмотрим позже, работает ли это
656+ Proc := TProcess.Create(nil); //Создаем новый процесс
657+ try
658+ Proc.Options := [poUsePipes, poStderrToOutPut]; //Используем pipes для перенаправления программных stdin, stdout, stderr
659+ Proc.CommandLine := 'sudo -S ls /root'; //Запускаем ls /root как root с помощью sudo
660+ // -S заставляет sudo читать пароль из stdin
661+ Proc.Execute; //Запускаем его. sudo теперь, вероятно, попросит пароль
662+
663+ // пишем пароль в stdin sudo программы:
664+ SudoPassword := SudoPassword + LineEnding;
665+ Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
666+ SudoPassword := '%*'; // короткая строка, надеюсь, это немного зашифрует память; примечание: использование PChars более надежно
667+ SudoPassword := ''; // и сделает программу немного более безопасной от слежки?!?
668+
669+ // основной цикл для чтения вывода из stdout и stderr sudo
670+ while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
671+ (Proc.Stderr.NumBytesAvailable > 0) do
672+ begin
673+ // читаем stdout и пишем в наш stdout
674+ while Proc.Output.NumBytesAvailable > 0 do
675+ begin
676+ ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Читаем до буфера, не более
677+ Proc.Output.Read(CharBuffer, ReadCount);
678+ Write(StdOut, Copy(CharBuffer, 0, ReadCount));
679+ end;
680+ // читаем stderr и пишем в наш stderr
681+ while Proc.Stderr.NumBytesAvailable > 0 do
682+ begin
683+ ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Читаем до буфера, не более
684+ Proc.Stderr.Read(CharBuffer, ReadCount);
685+ Write(StdErr, Copy(CharBuffer, 0, ReadCount));
686+ end;
687+ end;
688+ ExitCode := Proc.ExitStatus;
689+ finally
690+ Proc.Free;
691+ Halt(ExitCode);
692+ end;
693+ end;
694+
695+begin
696+ RunsLsRoot;
697+end.
698+```
699+
700+Другие мысли: Без сомнения, было бы целесообразно проверить, запрашивает ли sudo пароль. Это можно последовательно проверить, установив переменную окружения SUDO_PROMPT в ту, которую мы наблюдаем при чтении стандартного вывода TProcess, чтобы избежать проблемы, связанной с тем, что приглашение будет различным для разных локалей. Установка переменной среды приводит к тому, что значения по умолчанию очищаются (наследуются от нашего процесса), поэтому мы должны при необходимости скопировать окружение из нашей программы.
701+
702+### Использование fdisk с sudo в Linux
703+
704+В следующем примере показано, как запустить fdisk на компьютере с Linux с помощью команды sudo для получения прав root.
705+
706+> **Note:** это только пример, и он не рассчитан на большой результат
707+
708+``` pascal
709+program getpartitioninfo;
710+
711+{Первоначально пример предоставлен пользователем wjackson153 с форума Lazarus. Пожалуйста, свяжитесь с ним в случае возникновения вопросов, замечаний и т.д. Пример преобразован для простоты понимания/краткости из фрагмента кода Lazarus в программу FPC пользователем BigChimp}
712+
713+Uses
714+ Classes, SysUtils, FileUtil, Process;
715+
716+var
717+ hprocess: TProcess;
718+ sPass: String;
719+ OutputLines: TStringList;
720+
721+begin
722+ sPass := 'yoursudopasswordhere'; // Вы должны изменить это на свой собственный пароль sudo
723+ OutputLines:=TStringList.Create; //... было бы неплохо убедиться с помощью блока try..finally,
724+ // что OutputLines освобожден ... То же самое для hProcess.
725+
726+ // Следующий пример откроет fdisk в фоновом режиме и даст нам информацию о разделе
727+ // Поскольку fdisk требует повышенных привилегий, нам нужно
728+ // передать наш пароль в качестве параметра sudo с помощью параметра -S,
729+ // поэтому он будет ждать, пока наша программа отправит наш пароль в sudo приложение
730+ hProcess := TProcess.Create(nil);
731+ // В Linux/Unix/macOS нам нужно указать полный путь к нашему исполняемому файлу:
732+ hProcess.Executable := '/bin/sh';
733+ // Теперь мы добавляем все параметры в командную строку:
734+ hprocess.Parameters.Add('-c');
735+ // Здесь мы передаем пароль в команду sudo, которая затем выполняет fdisk -l:
736+ hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');
737+ // Запускаем асинхронно (дожидаемся завершения процесса) и используем pipes, чтобы мы могли прочитать output pipe
738+ hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
739+ // Теперь запускаем:
740+ hProcess.Execute;
741+
742+ // hProcess должен запустить внешний исполняемый файл (потому что мы используем poWaitOnExit).
743+ // Теперь вы можете обработать output процесса (стандартный output и стандартный error), например:
744+ OutputLines.Add('stdout:');
745+ OutputLines.LoadFromStream(hprocess.Output);
746+ OutputLines.Add('stderr:');
747+ OutputLines.LoadFromStream(hProcess.Stderr);
748+ // Показываем output на экране:
749+ writeln(OutputLines.Text);
750+
751+ // Очищаем, чтобы избежать утечек памяти:
752+ hProcess.Free;
753+ OutputLines.Free;
754+
755+ //Ниже приведены некоторые примеры, как вы видите, мы можем передавать недопустимые символы, как если бы это было сделано из терминала
756+ //Даже, если вы где-то читали, что это невозможно, я уверяю вас, что при помощи этого метода вы сможете :)
757+
758+ //hprocess.Parameters.Add('ping -c 1 www.google.com');
759+ //hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');
760+
761+ //Использование QuotedStr() не является обязательным требованием, хотя делает его более читабельным кодом;
762+ //Вы можете использовать двойные кавычки и добиться того же эффекта
763+
764+ //hprocess.Parameters.Add('glxinfo | grep direct');
765+
766+ // Этот метод также можно использовать для установки приложений из вашего хранилища:
767+
768+ //hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name');
769+
770+ end.
771+```
772+
773+### Параметры, содержащие пробелы (замена кавычек оболочки Shell)
774+
775+В оболочке Linux можно записать в кавычки следующие аргументы:
776+
777+``` bash
778+gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram
779+```
780+
781+И GDB получит 3 аргумента (в дополнение к первому аргументу, который является полным путем к исполняемому файлу):
782+
783+1. --batch
784+2. --eval-command=info symbol 0x0000DDDD
785+3. полный путь к myprogram
786+
787+TProcess также может передавать параметры, содержащие пробелы, но использует другой стиль квотирования. Вместо того, чтобы заключать в кавычки только часть параметра, заключите в кавычки все из них. Вот так:
788+
789+``` bash
790+TProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';
791+```
792+
793+И также не забудьте передать только полный путь.
794+
795+Смотрите также эту дискуссию об этом: [здесь](http://bugs.freepascal.org/view.php?id=14446)
796+
797+## Альтернативные решения с помощью LCLIntf
798+
799+Иногда вам не нужно явно вызывать внешнюю программу, чтобы получить необходимую вам функциональность. Вместо того, чтобы открывать приложение и указывать документ для него, просто попросите ОС открыть документ, позволив ей использовать приложение по умолчанию, связанное с этим типом файла. Ниже приведены некоторые примеры.
800+
801+### Открытие документа в приложении по умолчанию
802+
803+В некоторых ситуациях вам нужно открыть какой-либо документ/файл с использованием приложения по умолчанию, а не выполнить определенную программу. Это зависит от запущенной операционной системы. Lazarus предоставляет платформ-независимую процедуру **OpenDocument**, которая будет делать это за вас. Ваше приложение продолжит работу, не дожидаясь закрытия процесса документа.
804+
805+``` pascal
806+uses LCLIntf;
807+...
808+OpenDocument('manual.pdf');
809+...
810+```
811+
812+- [[opendocument|справка по OpenDocument]]
813+
814+### Открытие web-страницы в web-браузере по умолчанию
815+
816+Просто передайте требуемый URL, *<http://>* в начале строки является необязательным при определенных условиях. Кроме того, передача имени файла дает те же результаты, что и OpenDocument()
817+
818+``` pascal
819+uses LCLIntf;
820+...
821+OpenURL('www.lazarus.freepascal.org/');
822+```
823+
824+См. также:
825+
826+- [[OpenURL|справка по OpenURL]]
827+
828+Кроме того, вы можете использовать **TProcess** как в данном примере:
829+
830+``` pascal
831+uses Process;
832+
833+procedure OpenWebPage(URL: string);
834+// Очевидно, вам нужно передать свой URL внутри кавычек "", например, "www.lazarus.freepascal.org"
835+var
836+ Browser, Params: string;
837+begin
838+ FindDefaultBrowser(Browser, Params);
839+ with TProcess.Create(nil) do
840+ try
841+ Executable := Browser;
842+ Params:=Format(Params, [URL]);
843+ Params:=copy(Params,2,length(Params)-2); // удаляем "", новая версия TProcess.Parameters делает это сама
844+ Parameters.Add(Params);
845+ Options := [poNoConsole];
846+ Execute;
847+ finally
848+ Free;
849+ end;
850+end;
851+```
852+
853+## См. также
854+
855+- [Документация по TProcess](http://lazarus-ccr.sourceforge.net/docs/fcl/process/tprocess.html)
856+- [[opendocument|OpenDocument]]
857+- [[OpenURL]]
858+- [[TProcessUTF8/ru|TProcessUTF8]]
859+- [[TXMLPropStorage/ru|TXMLPropStorage]]
860+- [[Webbrowser/ru|Webbrowser]]
861+- [Запуск чужой программы из своей](http://freepascal.ru/article/freepascal/20191121080000/) - статья на русскоязычном freepascal-ресурсе
862+
863+---
864+*From the Free Pascal wiki, page "Executing External Programs/ru", as it stood on 2023-06-19 after a change by Zoltanleo, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).*

Back to history