Befehlsausführung
Führen Sie externe Befehle und Shell-Skripte mit voller Kontrolle über I/O-Streams aus.
Für Executor-Konfiguration siehe Executor.
Laden
local exec = require("exec")
Einen Executor beschaffen
Holen Sie eine Prozess-Executor-Ressource nach ID:
local executor, err = exec.get("app:exec")
if err then
return nil, err
end
-- Executor verwenden
local proc = executor:exec("ls -la")
-- ...
-- Freigeben wenn fertig
executor:release()
| Parameter | Typ | Beschreibung |
|---|---|---|
id |
string | Ressourcen-ID |
Gibt zurück: Executor, error
Einen Prozess erstellen
Erstellen Sie einen neuen Prozess mit dem angegebenen Befehl:
-- Einfacher Befehl
local proc, err = executor:exec("echo 'Hello, World!'")
-- Mit Arbeitsverzeichnis
local proc = executor:exec("npm install", {
work_dir = "/app/project"
})
-- Mit Umgebungsvariablen
local proc = executor:exec("python script.py", {
work_dir = "/scripts",
env = {
PYTHONPATH = "/app/lib",
DEBUG = "true",
API_KEY = api_key
}
})
-- Shell-Skript ausführen
local proc = executor:exec("./deploy.sh production", {
work_dir = "/app/scripts",
env = {
DEPLOY_ENV = "production"
}
})
| Parameter | Typ | Beschreibung |
|---|---|---|
cmd |
string | Ausführbare Datei und literale Argumente |
options.work_dir |
string | Arbeitsverzeichnis |
options.env |
table | Umgebungsvariablen |
options.pty |
table | Ein Pseudoterminal für den Kindprozess allokieren |
options.process_group |
boolean | Kindprozess in einer eigenen Prozessgruppe starten, damit Signale auch Nachkommen erreichen; unter Windows nicht unterstützt |
Gibt zurück: Process, error
Der Prozess wird erstellt, aber nicht gestartet.
Befehlszerlegung
cmd wird mit shell-ähnlichem Quoting in eine ausführbare Datei und literale Argumente zerlegt: Einfache und doppelte Anführungszeichen gruppieren ein Wort, und ein Backslash escaped das folgende Zeichen. Es gibt keine Shell, also finden weder Variablenexpansion noch Globbing, Pipes oder Umleitungen statt. Ein nicht geschlossenes Anführungszeichen gibt errors.INVALID zurück.
-- Ein Argument mit Leerzeichen, literal übergeben
local proc = executor:exec("grep 'hello world' notes.txt")
-- $HOME wird als die fünf Zeichen $HOME übergeben, nicht expandiert
local proc = executor:exec("echo $HOME")
Um Shell-Funktionen zu nutzen, wird eine Shell explizit aufgerufen:
local proc = executor:exec("/bin/sh -c 'ls *.log | wc -l'")
PTY-Optionen
Das Allokieren eines PTY gibt dem Kindprozess ein echtes Terminal: Zeileneditierung, Job Control und Vollbildprogramme funktionieren wie in einer Shell.
local proc = executor:exec("/bin/bash --noprofile --norc", {
pty = {width = 100, height = 30, term = "xterm-256color"},
})
| Feld | Typ | Standard | Beschreibung |
|---|---|---|---|
width |
number | 80 | Anfängliche PTY-Spalten, 1 bis 65535 |
height |
number | 24 | Anfängliche PTY-Zeilen, 1 bis 65535 |
term |
string | keiner | TERM-Wert des Kindprozesses |
Breite mal Höhe darf 262.144 Zellen nicht überschreiten. Ein PTY-gestützter Prozess führt die Ausgabe des Kindprozesses zu einem einzigen Terminal-Stream zusammen; er wird über resize und attach_terminal gesteuert statt über die stdin/stdout-Pipe-Methoden.
start / wait
Starten Sie den Prozess und warten Sie auf Abschluss.
local proc = executor:exec("./build.sh")
local ok, err = proc:start()
if err then
return nil, err
end
local exit_code, err = proc:wait()
if err then
return nil, err
end
if exit_code ~= 0 then
return nil, errors.new({ kind = errors.INTERNAL, message = "Build failed with exit code: " .. exit_code })
end
stdout_stream / stderr_stream
Holen Sie Streams zum Lesen der Prozessausgabe.
local proc = executor:exec("./process-data.sh")
local stdout = proc:stdout_stream()
local stderr = proc:stderr_stream()
proc:start()
-- Alle stdout lesen
local output = {}
while true do
local chunk = stdout:read(4096)
if not chunk then break end
table.insert(output, chunk)
end
local result = table.concat(output)
-- Auf Fehler prüfen
local err_output = {}
while true do
local chunk = stderr:read(4096)
if not chunk then break end
table.insert(err_output, chunk)
end
local exit_code = proc:wait()
stdout:close()
stderr:close()
if exit_code ~= 0 then
return nil, errors.new({ kind = errors.INTERNAL, message = table.concat(err_output) })
end
return result
write_stdin
Schreiben Sie Daten an Prozess-stdin.
local proc = executor:exec("head -n 3")
local stdout = proc:stdout_stream()
proc:start()
proc:write_stdin("banana\napple\ncherry\n")
local lines = stdout:read()
proc:wait()
stdout:close()
Jeder Aufruf schreibt die angegebenen Bytes und kehrt zurück. Rufen Sie
close_stdin() auf, wenn der Kindprozess EOF erhalten muss:
local proc = assert(executor:exec("sort"))
local stdout = assert(proc:stdout_stream())
assert(proc:start())
assert(proc:write_stdin("banana\napple\n"))
assert(proc:close_stdin())
local sorted = assert(stdout:read())
close_stdin() ist idempotent. Spätere Schreibversuche schlagen fehl, weil die
Eingabeseite geschlossen ist. PTY-gestützte Prozesse bieten diese Pipe-Operation
nicht an.
done
Mit done() lässt sich das Ende beobachten, ohne das Prozess-Handle zu verbrauchen:
local proc = assert(executor:exec("./worker"))
assert(proc:start())
local exits = assert(proc:done())
local status, open = exits:receive()
if open then
print(status.code, status.signal, status.error)
end
Der zurückgegebene Kanal liefert einen Exit-Datensatz und schließt danach.
Wiederholte Aufrufe geben denselben Kanal zurück. Der Datensatz enthält code,
optional signal und nur dann error, wenn die Runtime das Ende nicht
beobachten konnte. Bei einem Signal ist der Code 128 + signal. Anders als
wait() lässt done() das Handle nutzbar; wait() nach der Zustellung gibt den
gespeicherten Code zurück.
signal / close
Senden Sie Signale oder geben Sie den Prozess frei.
local proc = executor:exec("./long-running-server.sh")
proc:start()
-- ... später, muss gestoppt werden ...
-- SIGTERM senden und das Handle freigeben
proc:close()
-- SIGKILL senden und das Handle freigeben
proc:close(true)
-- Oder ein bestimmtes Signal senden und das Handle behalten
local SIGINT = 2
proc:signal(SIGINT)
close(force?) sendet einem gestarteten Kindprozess SIGTERM, oder SIGKILL, wenn force wahr ist, und reapt ihn dann im Hintergrund, sodass der Aufruf nicht blockiert. Ein Kindprozess, der nach einer Gnadenfrist noch läuft, wird gekillt, damit das Reaping immer abschließt. Ein nicht gestartetes Handle wird einfach ungültig gemacht, und zweimaliges Schließen ist kein Fehler. Mit process_group richten sich Signale an die Gruppe und erreichen Nachkommen auch nach dem Ende des führenden Prozesses.
Vor dem Reaping bezogene Streams bleiben lesbar, bis ihr letzter Writer schließt,
auch wenn ein Nachkomme die Pipe geerbt hat. Nach close() melden
Prozessmethoden process closed; verwenden Sie done(), wenn das Ende wichtig
ist und das Handle nutzbar bleiben muss.
resize
Ändert die Größe des PTY eines PTY-gestützten Prozesses. Ein Pipe-gestützter Prozess gibt einen Fehler zurück.
local ok, err = proc:resize(120, 40)
| Parameter | Typ | Beschreibung |
|---|---|---|
width |
number | Spalten, 1 bis 65535 |
height |
number | Zeilen, 1 bis 65535 |
Gibt zurück: boolean, error
Damit wird die anfängliche Geometrie gesetzt, bevor der Prozess an eine Terminal-Session übergeben wird. Sobald eine Session den Prozess besitzt, wird ihr stattdessen ein resize-Ereignis gesendet.
attach_terminal
Hängt einen nicht gestarteten PTY-gestützten Prozess an das Terminal des aufrufenden Prozesses an und gibt eine TerminalSession zurück.
local exec = require("exec")
local tty = require("tty")
local executor = assert(exec.get("app:exec"))
local proc = assert(executor:exec("/bin/bash --noprofile --norc", {
pty = {term = "xterm-256color"},
}))
local session = assert(proc:attach_terminal())
Gibt zurück: TerminalSession, error
Der Aufruf verbraucht den Prozess: Die Session wird sein alleiniger Lebenszyklus-Eigentümer, und das ursprüngliche Handle kann nicht mehr verwendet werden. Die Session öffnet eine Surface auf dem aktuellen Terminal-Port und übernimmt PTY-Emulation, Eingabekodierung, Größenänderung, geordnete und erzwungene Beendigung sowie das Reaping. Sie benötigt einen Terminal-Port — einen Prozess auf einem Terminal-Host oder einen mit einem Viewport-Grant gespawnten Prozess — und schlägt fehl, wenn der Port keinen Eingabecontroller hat oder bereits eine offene Surface besitzt.
TerminalSession
| Methode | Gibt zurück | Beschreibung |
|---|---|---|
send(event) |
boolean, error |
Ein kanonisches TTY-Ereignis an den Kindprozess weiterleiten |
done() |
channel | Kanal, der einmal auslöst, wenn der Kindprozess endet |
status() |
string, error |
"running" oder "done", mit dem Fehler, falls er fehlgeschlagen ist |
close() |
boolean, error |
Beendigung eines laufenden Kindprozesses anfordern |
send akzeptiert die in TTY beschriebenen Key-, Mouse-, Resize-, Focus- und Paste-Datensätze. Ein Senden, nachdem der Kindprozess beendet ist, gibt einen Fehler zurück.
local channel = require("channel")
local events = assert(tty.events())
assert(tty.start())
local done = session:done()
while true do
local selected = channel.select({
events:case_receive(),
done:case_receive(),
})
if not selected.ok or selected.channel == done then break end
if selected.value.type == "close" then break end
assert(session:send(selected.value))
end
assert(session:close())
Berechtigungen
Exec-Operationen unterliegen der Sicherheitsrichtlinienauswertung.
| Aktion | Ressource | Beschreibung |
|---|---|---|
exec.get |
Executor-ID | Executor-Ressource beschaffen |
exec.run |
Befehl | Einen bestimmten Befehl ausführen |
exec.run wird gegen die rohe Befehlszeichenkette ausgewertet, mit den angeforderten Optionen als Metadaten:
| Schlüssel | Typ | Beschreibung |
|---|---|---|
work_dir |
string | Angefordertes Arbeitsverzeichnis, leer wenn nicht gesetzt |
env_names |
string[] | Namen der übergebenen Umgebungsvariablen, sortiert; Werte werden nicht offengelegt |
pty.requested |
boolean | Ob ein PTY angefordert wurde |
pty.width |
number | Aufgelöste PTY-Spalten, vorhanden wenn angefordert |
pty.height |
number | Aufgelöste PTY-Zeilen, vorhanden wenn angefordert |
pty.term |
string | Angeforderter TERM-Wert, vorhanden wenn angefordert |
Eine Policy kann damit einfache Befehle erlauben und zugleich diejenigen einschränken, die ein Terminal oder ein bestimmtes Arbeitsverzeichnis anfordern.
Fehler
| Bedingung | Art | Wiederholbar |
|---|---|---|
| Ungültige ID | errors.INVALID |
nein |
| Berechtigung verweigert | errors.INVALID |
nein |
| Prozess geschlossen | errors.INVALID |
nein |
| Prozess nicht gestartet | errors.INVALID |
nein |
| Bereits gestartet | errors.INVALID |
nein |
| Nicht geschlossenes Anführungszeichen im Befehl | errors.INVALID |
nein |
| Kein PTY am Prozess | errors.INVALID |
nein |
| Terminal-Port nicht verfügbar | errors.UNAVAILABLE |
nein |
Siehe Fehlerbehandlung für die Arbeit mit Fehlern.
Siehe auch
- Executor — Executor-Konfiguration
- TTY — Terminal-Ereignisse, Surfaces und Viewports
- Terminal-UI — eine Shell, die einen PTY-Kindprozess in einem Viewport hostet