Executing External Programs/pl
│
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) │
Przegląd: Porównanie
Istnieją różne sposoby dostępne w bibliotekach RTL, FCL i LCL dotyczące wykonywania zewnętrznego polecenia/procesu/programu.
Metoda | Biblioteka | Platformy | Pojedyncza linia | Cechy |
---|---|---|---|---|
ExecuteProcess | RTL | Wieloplatformowy | Tak | Bardzo ograniczone, synchroniczne. |
ShellExecute | WinAPI | Tylko MS Windows | Tak | Wiele. Może uruchamiać programy z podwyższonymi uprawnieniami administratora. |
fpsystem, fpexecve | Unix | Tylko Unix | ||
TProcess | FCL | Wieloplatformowy | Nie | Pełny. |
RunCommand | FCL | Wieloplatformowy Wymaga FPC 2.6.2+ | Tak | Obejmuje typowe użycie TProcess. |
OpenDocument | LCL | Wieloplatformowy | Tak | Tylko otwarty dokument. Dokument otworzy się z aplikacją powiązaną z podanym typem dokumentu. |
(Proces.)RunCommand
- Odnośnik do RunCommand
- Odnośnik do RunCommandInDir
W FPC 2.6.2 niektóre funkcje pomocnicze dla TProcess zostały dodane do modułu process w oparciu o wrappery (funkcje lub klasy opakowujące) używane w projekcie fpcup. Te funkcje są przeznaczone do użytku podstawowego i pośredniego, i mogą przechwytywać dane wyjściowe do pojedynczego ciągu string i w pełni obsługiwać duże dane wyjściowe.
Prosty przykład to:
program project1;
{$mode objfpc}{$H+}
uses
Process;
var
s : ansistring;
begin
if RunCommand('/bin/bash',['-c','echo $PATH'],s) then
writeln(s);
end.
Zauważ jednak, że nie wszystkie „wbudowane” polecenia powłoki (np. aliasy) działają, ponieważ aliasy domyślnie nie są rozwijane w powłokach nieinteraktywnych, a .bashrc nie jest odczytywany przez powłoki nieinteraktywne, chyba że ustawisz zmienną środowiskową BASH_ENV. tak więc poniższy przykąłd nie daje żadnych danych wyjściowych:
program project2;
{$mode objfpc}{$H+}
uses
Process;
var
s : ansistring;
begin
if RunCommand('/bin/bash',['-c','alias'],s) then
writeln(s);
end.
Przeciążony wariant RunCommand zwraca kod wyjścia programu. RunCommandInDir uruchamia polecenie w innym katalogu (ustawia p.CurrentDirectory):
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;
W FPC 3.2.0+ RunCommand otrzymał dodatkowe warianty, które pozwalają nadpisać TProcessOptions i WindowOptions.
Rozszerzenia RunCommand
W FPC 3.2.0+ implementacja RunCommand została uogólniona i ponownie zintegrowana z TProcess, aby umożliwić szybszą budowę własnych wariantów. Jako przykład wariant RunCommand z limitem czasu:
{$mode delphi}
uses classes, sysutils, process, dateutils;
type
{ TProcessTimeout }
TProcessTimeout = class(TProcess)
public
timeoutperiod: TTime;
timedout : boolean;
started : TDateTime;
procedure LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string);
end;
procedure TProcessTimeout.LocalnIdleSleep(Sender,Context : TObject;status:TRunCommandEventCode;const message:string);
begin
if status=RunCommandIdle then
begin
if (now-started)>timeoutperiod then
begin
timedout:=true;
Terminate(255);
exit;
end;
sleep(RunCommandSleepTime);
end;
end;
function RunCommandTimeout(const exename:TProcessString;const commands:array of TProcessString;out outputstring:string; Options : TProcessOptions = [];SWOptions:TShowWindowOptions=swoNone;timeout:integer=60):boolean;
Var
p : TProcessTimeout;
i,
exitstatus : integer;
ErrorString : String;
begin
p:=TProcessTimeout.create(nil);
p.OnRunCommandEvent:=p.LocalnIdleSleep;
p.timeoutperiod:=timeout/SecsPerDay;
if Options<>[] then
P.Options:=Options - [poRunSuspended,poWaitOnExit];
p.options:=p.options+[poRunIdle]; // potrzebne do uruchomienia zdarzenia RUNIDLE. Zobacz Zmiany Użytkownika dla wersji 3.2.0
P.ShowWindow:=SwOptions;
p.Executable:=exename;
if high(commands)>=0 then
for i:=low(commands) to high(commands) do
p.Parameters.add(commands[i]);
p.timedout:=false;
p.started:=now;
try
// główna pętla wariantów runcommand(), pierwotnie oparta na scenariuszu "dużego wyjścia" na wiki, ale stale rozwijana przez 5 lat.
result:=p.RunCommandLoop(outputstring,errorstring,exitstatus)=0;
if p.timedout then
result:=false;
finally
p.free;
end;
if exitstatus<>0 then result:=false;
end;
// przykładowe zastosowanie:
var
s : string;
begin
for s in FileList do
begin
if not RunCommandTimeout('someexe',['-v',s,'--output','dest\'+s],err,[],swoNone,60) then
begin
// nie udało się uruchomić lub upłynął limit czasu. np. movefile() do "wadliwego" katalogu.
end
else
begin
// ok, plik przeniesiony do "dobrego" katalogu.
end;
end;
end;
SysUtils.ExecuteProcess
(Wieloplatformowy)
Pomimo wielu ograniczeń, najprostszym sposobem uruchomienia programu (modalnego, bez potoków lub jakiejkolwiek formy sterowania) jest po prostu użycie:
SysUtils.ExecuteProcess(UTF8ToSys('/full/path/to/binary'), '', []);
Proces wywołujący działa synchronicznie: „zawiesza się” do momentu zakończenia działania zewnętrznego programu — ale może to być przydatne, jeśli wymagasz od użytkownika wykonania czegoś przed kontynuowaniem pracy w aplikacji. Aby uzyskać bardziej wszechstronne podejście, zapoznaj się z sekcją dotyczącą preferowanego międzyplatformowego RunCommand lub innej funkcjonalności TProcess, lub jeśli chcesz obsługiwać tylko system Windows, możesz użyć ShellExecute.
- Odniesienie do ExecuteProcess
MS Windows: CreateProcess, ShellExecute i WinExec
ShellExecute to standardowa funkcja systemu MS Windows (ShellApi.h) z dobrą dokumentacją na MSDN (zwróć uwagę na ich komentarze na temat inicjalizacji COM, jeśli odkryjesz, że ta jest niewiarygodna).
uses ..., ShellApi;
// Proste jednowierszowe (ignorowanie zwracanych błędów):
if ShellExecute(0,nil, PChar('"C:\my dir\prog.exe"'),PChar('"C:\somepath\some_doc.ext"'),nil,1) =0 then;
// Wykonaj plik wsadowy:
if ShellExecute(0,nil, PChar('cmd'),PChar('/c mybatch.bat'),nil,1) =0 then;
// Otwórz okno poleceń w danym folderze:
if ShellExecute(0,nil, PChar('cmd'),PChar('/k cd \path'),nil,1) =0 then;
// Otwórz adres URL strony internetowej w domyślnej przeglądarce za pomocą polecenia „start” (poprzez ukryte okno cmd):
if ShellExecute(0,nil, PChar('cmd'),PChar('/c start www.lazarus.freepascal.org/'),nil,0) =0 then;
// lub przydatna procedura:
procedure RunShellExecute(const prog,params:string);
begin
// parametry: ( Uchwyt, nil/'open'/'edit'/'find'/'explore'/'print', // 'open' nie zawsze jest potrzebny
// path+prog, parametry, folder roboczy,
// okno command 0=ukryte / 1=SW_SHOWNORMAL / 3=maks / 7=min) // dla stałych SW_ : uses ... Windows ...
if ShellExecute(0,'open',PChar(prog),PChar(params),PChar(extractfilepath(prog)),1) >32 then; //sukces
// zwracane wartości 0..32 są błędami
end;
Istnieje również ShellExecuteExW jako wersja WideChar, a ShellExecuteExA to wersja AnsiChar.
Opcja fMask może również używać SEE_MASK_DOENVSUBST lub SEE_MASK_FLAG_NO_UI lub SEE_MASK_NOCLOSEPROCESS itp.
Jeśli w Delphi używałeś ShellExecute do dokumentów takich jak dokumenty Word lub adresy URL, spójrz na funkcje open* (OpenURL itp.) w module lclintf (zobacz sekcję Zobacz także na dole tej strony).
Używanie ShellExecuteEx do podnoszenia uprawnień administratora
Jeśli potrzebujesz uruchomić zewnętrzny program z podwyższonymi uprawnieniami administratora, możesz użyć metody runas z alternatywną funkcją 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
// Przykład, który używa podwyższonego uprawnienia dla rundll, aby otworzyć Panel sterowania do programów i funkcji
RunAsAdmin(FormMain.Handle, 'rundll32.exe shell32.dll,Control_RunDLL appwiz.cpl', '');
end;
Unix fpsystem, fpexecve i shell
Te funkcje są zależne od platformy.
Linux.Shell/Unix.Shell był odpowiednikiem fpsystem w wersji 1.0.x ze słabo zdefiniowaną obsługą błędów i po dekadzie deprecjacji został ostatecznie usunięty. Prawie we wszystkich przypadkach może być zastąpiony przez fpsystem, który obsługuje więcej standardów POSIX, takich jak obsługa błędów.
TProcess
Możesz użyć TProcess do uruchamiania zewnętrznych programów. Niektóre z korzyści płynących z używania TProcess to:
- Jest niezależny od platformy.
- Potrafi czytać ze standardowego wyjścia i zapisywać na standardowe wejście.
- Możliwe jest oczekiwanie na zakończenie polecenia lub pozwolenie na jego działanie podczas działania programu.
Ważne informacje:
- TProcess nie jest terminalem/powłoką! Nie możesz bezpośrednio wykonywać skryptów lub przekierowywać wyjścia za pomocą operatorów takich jak „|”, „>”, „<”, „&” itp. Możliwe jest uzyskanie tych samych wyników za pomocą TProcess i za pomocą pascala - kilka przykładów poniżej.
- Przypuszczalnie w systemie Linux/Unix: musisz podać pełną ścieżkę do pliku wykonywalnego. Na przykład „/bin/cp” zamiast „cp”. Jeśli program znajduje się w standardowej PATH, możesz użyć funkcji FindDefaultExecutablePath z modułu FileUtil biblioteki LCL.
- W systemie Windows, jeśli polecenie znajduje się w ścieżce, nie musisz określać pełnej ścieżki.
- Odnośnik do TProcess
Najprostszy przykład
Wiele typowych przypadków zostało przygotowanych w funkcjach RunCommand. Zanim zaczniesz kopiować i wklejać poniższe przykłady, najpierw je sprawdź.
Prosty przykład
Ten przykład (który nie powinien być używany w środowisku produkcyjnym, patrz Czytanie dużych ilości danych wyjściowych lub, lepiej, RunCommand) pokazuje tylko, jak uruchomić zewnętrzny program, nic więcej:
// Jest to program demonstracyjny, który
// pokazuje, jak uruchomić zewnętrzny program.
program launchprogram;
// Tutaj dołączamy moduły zawierające przydatne
// funkcje i procedury, których będziemy potrzebować.
uses
Classes, SysUtils, Process;
// Definiuje zmienną "AProcess" jako zmienną typu "TProcess"
var
AProcess: TProcess;
// Tu zaczyna działać nasz program
begin
// Teraz utworzymy obiekt TProcess i przypiszemy go
// do zmiennej AProcess.
AProcess := TProcess.Create(nil);
// Powiedz nowemu AProcess, jakie jest polecenie do wykonania.
// Użyjmy kompilatora Free Pascal (dokładniej wersji x64)
// Lecz jeśli używasz kompilatora 32-bit zmień ppcx64 na ppc386
AProcess.Executable:= 'ppcx64';
// Przekaż opcję -h razem z ppcx64, więc faktycznie wykonywane
// jest polecenie 'ppcx64 -h':
AProcess.Parameters.Add('-h');
// Zdefiniujemy opcję działającą, w czasie wykonywania programu.
// Ta opcja sprawi, że nasz program nie będzie kontynuowany,
// dopóki program zewnętrzy, który uruchomimy, nie przestanie działać.
// /------------\
AProcess.Options := AProcess.Options + [poWaitOnExit];
// Teraz pozwól AProcess uruchomić program zewnętrzny
AProcess.Execute;
// Poniższy kod nie wykona się, dopóki ppcx64 nie przestanie działać.
AProcess.Free;
end.
Otóż to! Właśnie nauczyłeś się uruchamiać zewnętrzny program z wnętrza własnego programu.
Ulepszony przykład (ale jeszcze niepoprawny)
Wszystko fajnie, ale jak odczytać dane wyjściowe uruchomionego programu?
Cóż, rozszerzmy nieco nasz przykład, jak poniżej: Ten przykład jest prosty, więc możesz się z niego uczyć. Jednak nie używaj tego przykładu w kodzie produkcyjnym, ale użyj kodu z Czytanie dużych ilości danych wyjściowych.
// To jest
// WADLIWY
// program demonstracyjny, który pokazuje,
// jak uruchomić zewnętrzny program
// i odczytać z jego dane wyjściowe.
program launchprogram;
// Tutaj dołączamy moduły zawierające przydatne
// funkcje i procedury, których będziemy potrzebować.
uses
Classes, SysUtils, Process;
// Definiuje zmienną "AProcess" jako zmienną typu "TProcess"
// Teraz także dodajemy TStringList do przechowywania danych
// odczytanych z wyjścia programu.
var
AProcess: TProcess;
AStringList: TStringList;
// Tu zaczyna działać nasz program
begin
// Teraz utworzymy obiekt TProcess i przypiszemy go
// do zmiennej AProcess.
AProcess := TProcess.Create(nil);
// Powiedz nowemu AProcess, jakie jest polecenie do wykonania.
AProcess.Executable := '/usr/bin/ppcx64';
AProcess.Parameters.Add('-h');
// Zdefiniujemy opcję działającą, w czasie wykonywania programu.
// Ta opcja sprawi, że nasz program nie będzie kontynuowany,
// dopóki program zewnętrzy, który uruchomimy, nie przestanie działać.
// Dodatkowo teraz powiemy mu, że chcemy odczytać dane wyjściowe.
// /------------|-----------\
AProcess.Options := AProcess.Options + [poWaitOnExit, poUsePipes];
// Teraz, gdy AProcess wie, jaki jest wiersz poleceń, można go uruchomić.
AProcess.Execute;
// Po zakończeniu AProcess zostanie wykonana reszta programu.
// Teraz odczytaj wynik działania programu, za pomocą TStringList.
AStringList := TStringList.Create;
AStringList.LoadFromStream(AProcess.Output);
// Zapisz dane wyjściowe do pliku i zwolnij obiekt AStringList.
AStringList.SaveToFile('output.txt');
AStringList.Free;
// Teraz, gdy dane wyjściowe z procesu są przetworzone, także można je zwolnić.
AProcess.Free;
end.
Czytanie dużych ilości danych wyjściowych
W poprzednim przykładzie czekaliśmy, aż program się zakończy. Następnie czytaliśmy, co program zapisał na swoim wyjściu.
Załóżmy, że program zapisuje dużo danych na wyjściu. Następnie potok wyjściowy zapełnia się i wywoływany program czeka, aż potok zostanie odczytany.
Ale program wywołujący nie czyta z niego, dopóki wywoływany program się nie zakończy. Następuje impas.
Poniższy przykład nie używa zatem poWaitOnExit, ale odczytuje dane wyjściowe, gdy program jest nadal uruchomiony. Dane wyjściowe są przechowywane w strumieniu pamięci, który można później wykorzystać do odczytania danych wyjściowych do TStringList.
Jeśli chcesz odczytać dane wyjściowe z procesu zewnętrznego i nie możesz użyć RunCommand, jest to kod, który powinien być podstawą do użytku produkcyjnego. Jeśli używasz FPC 3.2.0+, parametryzowalna forma tej pętli jest dostępna jako metoda RunCommandLoop w TProcess. Zdarzenie OnRunCommandEvent można podłączyć, aby dalej modyfikować zachowanie.
program LargeOutputDemo;
{$mode objfpc}{$H+}
uses
Classes, SysUtils, Process; // Process to moduł, który zawiera TProcess
const
BUF_SIZE = 2048; // Rozmiar bufora do odczytu danych wyjściowych w porcjach
var
AProcess : TProcess;
OutputStream : TStream;
BytesRead : longint;
Buffer : array[1..BUF_SIZE] of byte;
begin
// Skonfiguruj proces; jako przykład używane jest rekurencyjne przeszukiwanie
// katalogów, ponieważ zwykle skutkuje to dużą ilością danych.
AProcess := TProcess.Create(nil);
// Polecenia dla Windows i *nix są różne, stąd dyrektywy $IFDEF
{$IFDEF Windows}
// W systemie Windows polecenie dir nie może być użyte bezpośrednio, ponieważ jest to
// wbudowane polecenie powłoki. Dlatego potrzebny jest cmd.exe i dodatkowe parametry.
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';
{$IFDEF Darwin}
AProcess.Parameters.Add('-recursive');
AProcess.Parameters.Add('-all');
{$ENDIF Darwin}
{$IFDEF Linux}
AProcess.Parameters.Add('--recursive');
AProcess.Parameters.Add('--all');
{$ENDIF Linux}
{$IFDEF FreeBSD}
AProcess.Parameters.Add('-R');
AProcess.Parameters.Add('-a');
{$ENDIF FreeBSD}
AProcess.Parameters.Add('-l');
{$ENDIF Unix}
// Aby można było przechwycić dane wyjściowe, należy użyć opcji procesu poUsePipes.
// Nie można użyć opcji procesu poWaitOnExit, ponieważ zablokuje to program główny,
// uniemożliwiając mu odczytanie danych wyjściowych procesu zewnętrznego.
AProcess.Options := [poUsePipes];
// Uruchom proces (uruchom polecenie dir dla Windows lub ls dla Linux/Unix)
AProcess.Execute;
// Utwórz obiekt strumienia, w którym będą przechowywane wygenerowane dane wyjściowe.
// Może to być również strumień plikowy do bezpośredniego zapisywania danych wyjściowych na dysku.
OutputStream := TMemoryStream.Create;
// Wszystkie wygenerowane dane wyjściowe z AProcess są odczytywane w pętli,
// dopóki nie będzie dostępnych więcej danych
repeat
// Pobierz nowe dane z procesu do maksymalnego rozmiaru buforu, który został przydzielony.
// Zauważ, że wszystkie wywołania Read(...) będą blokowane z wyjątkiem ostatniego, które zwraca 0 (zero).
BytesRead := AProcess.Output.Read(Buffer, BUF_SIZE);
// Bajty, które zostały odczytane, dodaj do strumienia w celu późniejszego wykorzystania
OutputStream.Write(Buffer, BytesRead)
until BytesRead = 0; // Zakończ pętlę, jeśli nie ma więcej danych
// Proces się zakończył, więc można go posprzątać
AProcess.Free;
// Teraz, gdy wszystkie dane zostały odczytane, można z nich korzystać;
// na przykład po to, by zapisać je do pliku na dysku
with TFileStream.Create('output.txt', fmCreate) do
begin
OutputStream.Position := 0; // Wymagane, aby upewnić się, że wszystkie dane będą kopiowane od początku
CopyFrom(OutputStream, OutputStream.Size);
Free
end;
// Lub dane mogą być wyświetlane na ekranie
with TStringList.Create do
begin
OutputStream.Position := 0; // Wymagane, aby upewnić się, że wszystkie dane będą kopiowane od początku
LoadFromStream(OutputStream);
writeln(Text);
writeln('--- Liczba linii = ', Count, '----');
Free
end;
// Sprzątanie
OutputStream.Free;
end.
Zauważ, że powyższe można również osiągnąć za pomocą RunCommand:
var s: string;
...
RunCommand('c:\windows\system32\cmd.exe', ['/c', 'dir /s c:\windows'], s);
Korzystanie z wejścia i wyjścia TProcess
Zobacz przykładowe demo procesu w Lazarus-CCR SVN.
Wskazówki dotyczące korzystania z TProcess
Podczas tworzenia programu wieloplatformowego nazwę pliku wykonywalnego specyficzną dla systemu operacyjnego można ustawić za pomocą dyrektyw „{$IFDEF}” i „{$ENDIF}”. Przykład:
{...}
AProcess := TProcess.Create(nil)
{$IFDEF WIN32}
AProcess.Executable := 'calc.exe';
{$ENDIF}
{$IFDEF LINUX}
AProcess.Executable := FindDefaultExecutablePath('kcalc');
{$ENDIF}
AProcess.Execute;
{...}
macOS pokazuje pakiet aplikacji na pierwszym planie
Możesz uruchomić pakiet aplikacji przez TProcess, uruchamiając plik wykonywalny w pakiecie. Na przykład:
AProcess.Executable:='/Applications/iCal.app/Contents/MacOS/iCal';
Spowoduje to uruchomienie Kalendarza, ale okno będzie znajdować się za bieżącą aplikacją. Aby uzyskać aplikację na pierwszym planie, możesz użyć narzędzia open z parametrem -n:
AProcess.Executable:='/usr/bin/open';
AProcess.Parameters.Add('-n');
AProcess.Parameters.Add('-a'); // opcjonalny: określa aplikację do użycia; przeszukuje tylko katalogi aplikacji
AProcess.Parameters.Add('-W'); // opcjonalnie: open czeka, aż aplikacje, które otwiera (lub były już otwarte) zostaną zamknięte
AProcess.Parameters.Add('Pages.app'); // dołączenie .app jest opcjonalne
Jeśli Twoja aplikacja potrzebuje parametrów, możesz przekazać wraz z open parametr --args, po którym wszystkie dalsze parametry są przekazywane do aplikacji:
AProcess.Parameters.Add('--args');
AProcess.Parameters.Add('argument1');
AProcess.Parameters.Add('argument2');
Zobacz także: macOS polecenie open.
Uruchom oddzielny program
Normalnie program uruchomiony przez twoją aplikację jest procesem potomnym i zostaje zabity, gdy twoja aplikacja zostanie zabita. Jeśli chcesz uruchomić samodzielny program, który nadal będzie działał, możesz użyć:
var
Process: TProcess;
I: Integer;
begin
Process := TProcess.Create(nil);
try
Process.InheritHandles := False;
Process.Options := [];
Process.ShowWindow := swoShow;
// Skopiuj domyślne zmienne środowiskowe, w tym zmienną DISPLAY, aby aplikacja GUI działała
for I := 1 to GetEnvironmentVariableCount do
Process.Environment.Add(GetEnvironmentString(I));
Process.Executable := '/usr/bin/gedit';
Process.Execute;
finally
Process.Free;
end;
end;
Przykład „rozmowy” z procesem aspell
Wewnątrz kodu źródłowego pasdoc można znaleźć dwa moduły, które sprawdzają pisownię poprzez „rozmawianie” przez potoki z uruchomionym procesem aspell:
- Moduł PasDoc_ProcessLineTalk.pas implementuje klasę TProcessLineTalk, potomka TProcess, która może być z łatwością używana do komunikacji z dowolnym procesem linia po linii.
- Moduł PasDoc_Aspell.pas implementuje klasę TAspellProcess, która sprawdza pisownię za pomocą bazowej instancji TProcessLineTalk do wykonania aspell i komunikacji z uruchomionym procesem aspell.
Obydwa moduły są raczej niezależne od pozostałych źródeł pasdoc, więc mogą służyć jako rzeczywiste przykłady użycia TProcess do uruchamiania i komunikacji przez potoki z innym programem.
Zastępowanie operatorów powłoki, takich jak „| < >”
Czasami chcesz uruchomić bardziej skomplikowane polecenie, które przesyła dane do innego polecenia lub do pliku. Coś podobnego do
ShellExecute('firstcommand.exe | secondcommand.exe');
lub
ShellExecute('dir > output.txt');
Wykonanie tego za pomocą TProcess nie zadziała. tj:
// to nie zadziała!
Process.CommandLine := 'firstcommand.exe | secondcommand.exe';
Process.Execute;
Dlaczego używanie specjalnych operatorów do przekierowywania wyjścia nie działa?
TProcess nie jest środowiskiem powłoki, tylko procesem. To nie dwa procesy, to tylko jeden. Możliwe jest jednak przekierowanie danych wyjściowych dokładnie tak, jak chcesz. Zobacz następną sekcję.
Jak przekierować wyjście za pomocą TProcess
Możesz przekierować dane wyjściowe polecenia do innego polecenia, używając osobnej instancji TProcess dla każdego polecenia.
Oto przykład, który wyjaśnia, jak przekierować dane wyjściowe jednego procesu do drugiego. Aby przekierować wyjście procesu do pliku/strumienia, zobacz przykład Czytanie dużych ilości danych wyjściowych.
Możesz nie tylko przekierować „normalne” wyjście (znane również jako stdout), ale możesz także przekierować wyjście błędu (stderr), jeśli określisz opcję poStderrToOutPut, jak to widać w opcjach drugiego procesu.
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+ ' -');
// byłoby to to samo co "pwd | grep / -"
FirstProcess.Execute;
SecondProcess.Execute;
while FirstProcess.Running or (FirstProcess.Output.NumBytesAvailable > 0) do
begin
if FirstProcess.Output.NumBytesAvailable > 0 then
begin
// upewnij się, że nie czytamy więcej danych niż przydzieliliśmy w buforze
ReadSize := FirstProcess.Output.NumBytesAvailable;
if ReadSize > SizeOf(Buffer) then
ReadSize := SizeOf(Buffer);
// teraz wczytaj dane wyjściowe do bufora
ReadCount := FirstProcess.Output.Read(Buffer[0], ReadSize);
// i zapisz bufor do drugiego procesu
SecondProcess.Input.Write(Buffer[0], ReadCount);
// jeśli SecondProcess zapisuje dużo danych do swojego wyjścia,
// powinniśmy odczytać te dane tutaj, aby zapobiec zakleszczeniu,
// patrz poprzedni przykład "Czytanie dużych ilości danych wyjściowych"
end;
end;
// Zamknij dane wejściowe w SecondProcess,
// aby zakończyć przetwarzanie swoich danych
SecondProcess.CloseInput;
// i poczekaj, aż się zakończy, lecz uważaj, jakie polecenie uruchamiasz,
// ponieważ może się ono nie zakończyć, gdy jego dane wejściowe są zamknięte,
// to następująca linia może zapętlić się w nieskończoność
while SecondProcess.Running do
Sleep(1);
// Gotowe! Reszta programu jest tylko po to, aby przykład był bardziej 'użyteczny'
// użyjemy ponownie Buffer, aby wyprowadzić wyjście SecondProcess do *tego* programu stdout
WriteLn('Rozpoczęcie wyjścia grep:');
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 nie znalazł tego, czego szukaliśmy. ', SecondProcess.ExitStatus);
WriteLn('Zakończenie wyjścia grep:');
// zwolnij obiekty naszych procesów
FirstProcess.Free;
SecondProcess.Free;
end.
W ten sposób możesz przekierować dane wyjściowe z jednego programu do drugiego.
Uwagi
Ten przykład może wydawać się przesadny, ponieważ możliwe jest uruchamianie „skomplikowanych” poleceń przy użyciu powłoki przy pomocy TProcess, takich jak:
Process.Commandline := 'sh -c "pwd | grep / -"';
Ale nasz przykład jest bardziej wieloplatformowy, ponieważ nie wymaga modyfikacji, aby działać w systemie Windows lub Linux itp. Polecenie „sh” może, ale nie musi istnieć na twojej platformie i jest ogólnie dostępny tylko na platformach *nix. W naszym przykładzie mamy również większą elastyczność, ponieważ możesz czytać i zapisywać z/do wejścia, wyjścia i stderr każdego procesu z osobna, co może być bardzo korzystne dla twojego projektu.
Przekierowywanie wejścia i wyjścia oraz działanie z rootem
Częstym problemem w systemach Unix (FreeBSD, macOS) i Linux jest to, że chcesz uruchomić jakiś program na koncie root (lub, bardziej ogólnie, na innym koncie użytkownika). Przykładem może być uruchomienie polecenia ping.
Jeśli możesz użyć do tego sudo, możesz dostosować następujący przykład zaadaptowany z postu opublikowanego przez andymana na forum ([1]). Ten przykład uruchamia polecenie ls
w katalogu /root
, ale oczywiście można go dostosować.
Lepszym sposobem na to jest użycie pakietu policykit, który powinien być dostępny we wszystkich najnowszych Linuksach. Zobacz wątek na forum, aby uzyskać szczegółowe informacje.
Duże części tego kodu są podobne do wcześniejszego przykładu, ale pokazuje również, jak przekierować stdout i stderr procesu wywoływanego oddzielnie na stdout i stderr naszego własnego kodu.
program rootls;
{ Demonstracja użycia TProcess, przekierowania stdout/stderr do naszego stdout/stderr,
wywoływania sudo w systemach FreeBSD/Linux/macOS i dostarczania danych wejściowych na stdin}
{$mode objfpc}{$H+}
uses
Classes,
Math, {for min}
Process;
procedure RunsLsRoot;
var
Proc: TProcess;
CharBuffer: array [0..511] of char;
ReadCount: integer;
ExitCode: integer;
SudoPassword: string;
begin
WriteLn('Please enter the sudo password:');
Readln(SudoPassword);
ExitCode := -1; //Zacznij od porażki, zobaczmy później, czy to zadziała
Proc := TProcess.Create(nil); //Utwórz nowy proces
try
Proc.Options := [poUsePipes, poStderrToOutPut]; //Użyj potoków, aby przekierować stdin,stdout,stderr
Proc.CommandLine := 'sudo -S ls /root'; //Uruchom ls /root jako root za pomocą sudo
// -S powoduje, że sudo odczytuje hasło z stdin.
Proc.Execute; //Uruchom to. sudo prawdopodobnie poprosi teraz o hasło
// wpisz hasło do stdin programu sudo:
SudoPassword := SudoPassword + LineEnding;
Proc.Input.Write(SudoPassword[1], Length(SudoPassword));
SudoPassword := '%*'; // krótki string, mam nadzieję, że trochę pomiesza pamięć; uwaga: używanie PChars jest bardziej niezawodne
SudoPassword := ''; // i sprawić, by program był nieco bezpieczniejszy przed węszeniem?!?
// główna pętla do odczytu wyjścia z stdout i stderr dla sudo
while Proc.Running or (Proc.Output.NumBytesAvailable > 0) or
(Proc.Stderr.NumBytesAvailable > 0) do
begin
// odczytaj stdout i zapisz do naszego stdout
while Proc.Output.NumBytesAvailable > 0 do
begin
ReadCount := Min(512, Proc.Output.NumBytesAvailable); //Czytaj do bufora, nie więcej
Proc.Output.Read(CharBuffer, ReadCount);
Write(StdOut, Copy(CharBuffer, 0, ReadCount));
end;
// odczytaj stderr i zapisz do naszego stderr
while Proc.Stderr.NumBytesAvailable > 0 do
begin
ReadCount := Min(512, Proc.Stderr.NumBytesAvailable); //Czytaj do bufora, nie więcej
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.
Inne przemyślenia: Bez wątpienia wskazane byłoby sprawdzenie, czy sudo rzeczywiście pyta o hasło. Można to konsekwentnie sprawdzać, ustawiając zmienną środowiskową SUDO_PROMPT na coś, na co zwracamy uwagę podczas czytania stdout TProcess, unikając problemu związanego z różnymi znakami zachęty dla różnych lokalizacji. Ustawienie zmiennej środowiskowej powoduje wyczyszczenie wartości domyślnych (odziedziczonych z naszego procesu), więc w razie potrzeby musimy skopiować środowisko z naszego programu.
Używanie fdisk z sudo w systemie Linux
Poniższy przykład pokazuje, jak uruchomić fdisk na komputerze z systemem Linux za pomocą polecenia sudo, aby uzyskać uprawnienia roota. Uwaga: jest to tylko przykład i nie obejmuje dużej ilości danych na wyjściu.
program getpartitioninfo;
{Pierwotnie napisany przez użytkownika forum Lazarus wjackon153. Prosimy o kontakt w przypadku pytań, uwag itp.
Zmodyfikowan z fragmentu kodu Lazarusa na program FPC dla ułatwienia zrozumienia/zwięzłości przez BigChimp}
Uses
Classes, SysUtils, FileUtil, Process;
var
hprocess: TProcess;
sPass: String;
OutputLines: TStringList;
begin
sPass := 'yoursudopasswordhere'; // Musisz to zmienić na własne hasło sudo
OutputLines:=TStringList.Create; //dla pewności warto zamknąć to w bloku try...finally...end
// OutputLines został utworzony ... To samo dla hProcess.
// Poniższy przykład otworzy fdisk w tle i da nam informacje o partycjach
// Ponieważ fdisk wymaga podwyższonych uprawnień, musimy przekazać
// nasze hasło jako parametr do sudo za pomocą opcji -S, więc proces
// poczeka, aż nasz program wyśle nasze hasło do aplikacji sudo
hProcess := TProcess.Create(nil);
// W systemach Linux/Unix/FreeBSD/macOS musimy podać pełną ścieżkę do naszego pliku wykonywalnego:
hProcess.Executable := '/bin/sh';
// Teraz dodajemy wszystkie parametry w wierszu poleceń:
hprocess.Parameters.Add('-c');
// Tutaj przesyłamy potokiem hasło do polecenia sudo, które następnie wykonuje fdisk -l:
hprocess.Parameters.add('echo ' + sPass + ' | sudo -S fdisk -l');
// Uruchom asynchronicznie (poczekaj na zakończenie procesu) i użyj potoków, abyśmy mogli odczytać potok wyjściowy
hProcess.Options := hProcess.Options + [poWaitOnExit, poUsePipes];
// Teraz wykonaj:
hProcess.Execute;
// hProcess powinien teraz uruchomić zewnętrzny plik wykonywalny (ponieważ używamy poWaitOnExit).
// Teraz możesz przetworzyć wyjście procesu (standardowe wyjście i standardowy błąd), np.:
OutputLines.Add('stdout:');
OutputLines.LoadFromStream(hprocess.Output);
OutputLines.Add('stderr:');
OutputLines.LoadFromStream(hProcess.Stderr);
// Pokaż dane wyjściowe na ekranie:
writeln(OutputLines.Text);
// Wyczyść, aby uniknąć wycieków pamięci:
hProcess.Free;
OutputLines.Free;
//Poniżej znajduje się kilka przykładów, jak widać, możemy przekazywać niedozwolone znaki, tak jak robimy to z terminala
//Nawet jeśli przeczytałeś gdzie indziej, że nie możesz upewnić się tą metodą, to możesz :)
//hprocess.Parameters.Add('ping -c 1 www.google.com');
//hprocess.Parameters.Add('ifconfig wlan0 | grep ' + QuotedStr('inet addr:') + ' | cut -d: -f2');
//Użycie QuotedStr() nie jest wymagane, chociaż zapewnia czystszy kod;
//możesz użyć podwójnego cudzysłowu i mieć ten sam efekt.
//hprocess.Parameters.Add('glxinfo | grep direct');
// Ta metoda może być również używana do instalowania aplikacji z repozytorium:
//hprocess.Parameters.add('echo ' + sPass + ' | sudo -S apt-get install -y pkg-name');
end.
Parametry zawierające spacje (Zastępowanie cudzysłowów powłoki)
W powłoce Linuksa możliwe jest pisanie cytowanych argumentów w następujący sposób:
gdb --batch --eval-command="info symbol 0x0000DDDD" myprogram
Polecenie GDB otrzyma 3 argumenty (oprócz pierwszego argumentu, który jest pełną ścieżką do pliku wykonywalnego):
- --batch
- --eval-command=info symbol 0x0000DDDD
- pełna ścieżka do myprogram
Najlepszym rozwiązaniem, aby uniknąć skomplikowanego cytowania, jest przejście na TProcess.Parameters.Add zamiast ustawiania wiersza poleceń dla nietrywialnych przypadków, np.
AProcess.Executable:='/usr/bin/gdb'; AProcess.Parameters.Add('--batch'); AProcess.Parameters.Add('--eval-command=info symbol 0x0000DDDD'); // zwróć tutaj uwagę na brak cytowania AProcess.Parameters.Add('/home/me/myprogram');
Pamiętaj też, aby używać tylko pełne ścieżki.
TProcess.Commandline obsługuje jednak tylko podstawowe cytowanie parametrów z cudzysłowami. Ujmij w apostrofy cały parametr zawierający spacje z podwójnymi cudzysłowami. Jak poniżej:
AProcess.CommandLine := '/usr/bin/gdb --batch "--eval-command=info symbol 0x0000DDDD" /home/me/myprogram';
Właściwość .CommandLine jest przestarzała, a raporty o błędach do obsługi bardziej skomplikowanych przypadków cytowania nie będą akceptowane.
Alternatywy LCLIntf
Czasami nie trzeba jawnie wywoływać zewnętrznego programu, aby uzyskać potrzebną funkcjonalność. Zamiast otwierać aplikację i określać dokument, który ma być z nią powiązany, po prostu poproś system operacyjny o otwarcie dokumentu i pozwól mu użyć domyślnej aplikacji powiązanej z tym typem pliku. Poniżej kilka przykładów.
Otwieranie dokumentu w domyślnej aplikacji
W niektórych sytuacjach musisz otworzyć jakiś dokument/plik przy użyciu domyślnej aplikacji z nim powiązanej, zamiast uruchamiać konkretny program. To zależy od działającego systemu operacyjnego. Lazarus zapewnia niezależną od platformy procedurę OpenDocument, która zajmie się tym za Ciebie. Twoja aplikacja będzie nadal działać, nie czekając na zamknięcie procesu tworzenia dokumentu.
uses LCLIntf;
...
OpenDocument('manual.pdf');
...
Otwieranie strony web w domyślnej przeglądarce internetowej
Aby to zrobić, po prostu przekaż wymagany adres URL. Przedrostek protokołu http:// wydaje się w pewnych okolicznościach opcjonalny. Ponadto przekazanie nazwy pliku wydaje się dawać takie same wyniki jak OpenDocument()
uses LCLIntf;
...
OpenURL('www.lazarus.freepascal.org/');
Zobacz także:
Lub możesz użyć TProcess w taki sposób:
uses Process;
procedure OpenWebPage(URL: string);
// Możliwe, że musisz podać swój adres URL w cudzysłowiu np. "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); // usuń znaki "", nowa wersja TProcess.Parameters robi to sama
Parameters.Add(Params);
Options := [poNoConsole];
Execute;
finally
Free;
end;
end;