コマンド実行
I/Oストリームを完全に制御して外部コマンドとシェルスクリプトを実行します。
エグゼキュータの設定についてはエグゼキュータを参照。
ロード
local exec = require("exec")
エグゼキュータの取得
IDでプロセスエグゼキュータリソースを取得します:
local executor, err = exec.get("app:exec")
if err then
return nil, err
end
-- エグゼキュータを使用
local proc = executor:exec("ls -la")
-- ...
-- 完了時に解放
executor:release()
| パラメータ | 型 | 説明 |
|---|---|---|
id |
string | リソースID |
戻り値: Executor, error
プロセスの作成
指定されたコマンドで新しいプロセスを作成します:
-- シンプルなコマンド
local proc, err = executor:exec("echo 'Hello, World!'")
-- 作業ディレクトリ付き
local proc = executor:exec("npm install", {
work_dir = "/app/project"
})
-- 環境変数付き
local proc = executor:exec("python script.py", {
work_dir = "/scripts",
env = {
PYTHONPATH = "/app/lib",
DEBUG = "true",
API_KEY = api_key
}
})
-- シェルスクリプトを実行
local proc = executor:exec("./deploy.sh production", {
work_dir = "/app/scripts",
env = {
DEPLOY_ENV = "production"
}
})
| パラメータ | 型 | 説明 |
|---|---|---|
cmd |
string | 実行ファイルとリテラルの引数 |
options.work_dir |
string | 作業ディレクトリ |
options.env |
table | 環境変数 |
options.pty |
table | 子プロセス用の疑似ターミナルを割り当てる |
options.process_group |
boolean | 子プロセスを独自のプロセスグループで開始し、シグナルが子孫にも届くようにする。Windowsでは非対応 |
戻り値: Process, error
プロセスは作成されますが、開始はされません。
コマンドの解析
cmdは、シェル風のクォート規則で実行ファイルとリテラルの引数に分割されます。シングルクォートとダブルクォートは単語をまとめ、バックスラッシュは後続の1文字をエスケープします。シェルは介在しないため、変数展開、グロブ、パイプ、リダイレクトは行われません。閉じられていないクォートはerrors.INVALIDを返します。
-- スペースを含む1つの引数がリテラルとして渡される
local proc = executor:exec("grep 'hello world' notes.txt")
-- $HOMEは展開されず、$HOMEという5文字として渡される
local proc = executor:exec("echo $HOME")
シェルの機能を使うには、シェルを明示的に呼び出します:
local proc = executor:exec("/bin/sh -c 'ls *.log | wc -l'")
PTYオプション
PTYを割り当てると、子プロセスは実際のターミナルを得ます。行編集、ジョブ制御、フルスクリーンプログラムがシェル上と同じように動作します。
local proc = executor:exec("/bin/bash --noprofile --norc", {
pty = {width = 100, height = 30, term = "xterm-256color"},
})
| フィールド | 型 | デフォルト | 説明 |
|---|---|---|---|
width |
number | 80 | PTYの初期カラム数(1〜65535) |
height |
number | 24 | PTYの初期行数(1〜65535) |
term |
string | なし | 子プロセスのTERM値 |
幅×高さは262,144セルを超えられません。PTYを持つプロセスは子プロセスの出力を単一のターミナルストリームにまとめます。stdin/stdoutのパイプメソッドではなく、resizeとattach_terminalで操作してください。
start / wait
プロセスを開始して完了を待機します。
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
プロセス出力を読み取るストリームを取得します。
local proc = executor:exec("./process-data.sh")
local stdout = proc:stdout_stream()
local stderr = proc:stderr_stream()
proc:start()
-- すべてのstdoutを読み取り
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)
-- エラーをチェック
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
プロセスの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()
各呼び出しは指定されたバイト列を書き込んで戻ります。子プロセスにEOFを認識させる必要がある場合は、close_stdin()を呼び出します:
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()は冪等です。入力側を閉じた後の書き込みは失敗します。PTYベースのプロセスではこのパイプ操作を利用できません。
done
プロセスハンドルを消費せずに終了を監視するには、done()を使用します:
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
返されるチャネルは終了レコードを1件送信した後に閉じます。繰り返し呼び出すと同じチャネルが返されます。レコードにはcode、任意のsignalが含まれ、ランタイムが終了を監視できなかった場合のみerrorが含まれます。シグナルによる終了では、コードは128 + signalになります。wait()と異なり、done()はハンドルを引き続き使用可能にするため、ストリーム、signal()、close()を利用できます。通知後にwait()を呼び出すと、記録されたコードを返します。
signal / close
シグナルを送信またはプロセスを解放します。
local proc = executor:exec("./long-running-server.sh")
proc:start()
-- ... 後でそれを停止する必要がある ...
-- SIGTERMを送信してハンドルを解放
proc:close()
-- SIGKILLを送信してハンドルを解放
proc:close(true)
-- または特定のシグナルを送信し、ハンドルは保持
local SIGINT = 2
proc:signal(SIGINT)
close(force?)は、開始済みの子プロセスにSIGTERMを(forceがtrueの場合はSIGKILLを)送信し、その後バックグラウンドで回収するため、呼び出しはブロックしません。猶予期間を過ぎても実行中の子プロセスはkillされ、回収が必ず完了します。開始されていないハンドルは単に無効化され、二重にクローズしてもエラーにはなりません。process_groupを有効にすると、シグナルはグループを対象とし、リーダーの終了後も子孫に届きます。
回収前に取得したストリームは、最後の書き込み側が閉じるまで読み取り可能です。パイプを継承した子孫プロセスも含まれます。close()後はプロセスのメソッドがprocess closedを報告します。終了が重要でハンドルを引き続き使用する必要がある場合は、done()を使用してください。
resize
PTYを持つプロセスのPTYをリサイズします。パイプベースのプロセスではエラーを返します。
local ok, err = proc:resize(120, 40)
| パラメータ | 型 | 説明 |
|---|---|---|
width |
number | カラム数(1〜65535) |
height |
number | 行数(1〜65535) |
戻り値: boolean, error
プロセスをターミナルセッションに渡す前に、初期ジオメトリを設定するために使用します。セッションがプロセスを所有した後は、代わりにresizeイベントをセッションへ送信してください。
attach_terminal
開始されていないPTYベースのプロセスを呼び出し元プロセスのターミナルにアタッチし、TerminalSessionを返します。
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())
戻り値: TerminalSession, error
この呼び出しはプロセスを消費します。セッションが唯一のライフサイクル所有者となり、元のハンドルは使用できなくなります。セッションは現在のターミナルポート上にサーフェスを開き、PTYエミュレーション、入力エンコーディング、リサイズ、グレースフルおよび強制終了、回収を所有します。セッションにはターミナルポートが必要で(ターミナルホスト上のプロセス、またはビューポートグラント付きでスポーンされたプロセス)、ポートに入力コントローラがない場合や既にサーフェスが開かれている場合は失敗します。
TerminalSession
| メソッド | 戻り値 | 説明 |
|---|---|---|
send(event) |
boolean, error |
正規化されたTTYイベントを1つ子プロセスへ転送する |
done() |
channel | 子プロセスの終了時に一度だけ発火するチャネル |
status() |
string, error |
"running"または"done"。失敗した場合はその失敗エラーを伴う |
close() |
boolean, error |
実行中の子プロセスの終了を要求する |
sendはTTYで説明されているキー、マウス、リサイズ、フォーカス、ペーストの各レコードを受け付けます。子プロセスの終了後に送信するとエラーを返します。
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())
権限
Exec操作はセキュリティポリシー評価の対象です。
| アクション | リソース | 説明 |
|---|---|---|
exec.get |
エグゼキュータID | エグゼキュータリソースを取得 |
exec.run |
コマンド | 特定のコマンドを実行 |
exec.runは生のコマンド文字列に対して評価され、要求されたオプションがメタデータとして渡されます:
| キー | 型 | 説明 |
|---|---|---|
work_dir |
string | 要求された作業ディレクトリ。未設定の場合は空 |
env_names |
string[] | 渡された環境変数の名前(ソート済み)。値は公開されない |
pty.requested |
boolean | PTYが要求されたかどうか |
pty.width |
number | 解決されたPTYのカラム数。要求された場合に存在 |
pty.height |
number | 解決されたPTYの行数。要求された場合に存在 |
pty.term |
string | 要求されたTERM値。要求された場合に存在 |
したがってポリシーは、通常のコマンドは許可しつつ、ターミナルや特定の作業ディレクトリを要求するコマンドだけを制限できます。
エラー
| 条件 | 種別 | 再試行可能 |
|---|---|---|
| 無効なID | errors.INVALID |
no |
| 権限拒否 | errors.INVALID |
no |
| プロセスがクローズ済み | errors.INVALID |
no |
| プロセスが開始されていない | errors.INVALID |
no |
| 既に開始済み | errors.INVALID |
no |
| コマンド内のクォートが閉じられていない | errors.INVALID |
no |
| プロセスにPTYがない | errors.INVALID |
no |
| ターミナルポートが利用できない | errors.UNAVAILABLE |
no |
エラーの処理についてはエラー処理を参照。