Execução de Comandos
Execute comandos externos e scripts shell com controle total sobre streams de I/O.
Para configuração de executor, veja Executor.
Carregamento
local exec = require("exec")
Obtendo um Executor
Obter um recurso de executor de processo pelo ID:
local executor, err = exec.get("app:exec")
if err then
return nil, err
end
-- Usar executor
local proc = executor:exec("ls -la")
-- ...
-- Liberar quando terminar
executor:release()
| Parâmetro | Tipo | Descrição |
|---|---|---|
id |
string | ID do recurso |
Retorna: Executor, error
Criando um Processo
Criar um novo processo com o comando específicado:
-- Comando simples
local proc, err = executor:exec("echo 'Hello, World!'")
-- Com diretorio de trabalho
local proc = executor:exec("npm install", {
work_dir = "/app/project"
})
-- Com variaveis de ambiente
local proc = executor:exec("python script.py", {
work_dir = "/scripts",
env = {
PYTHONPATH = "/app/lib",
DEBUG = "true",
API_KEY = api_key
}
})
-- Executar script shell
local proc = executor:exec("./deploy.sh production", {
work_dir = "/app/scripts",
env = {
DEPLOY_ENV = "production"
}
})
| Parâmetro | Tipo | Descrição |
|---|---|---|
cmd |
string | Executavel e argumentos literais |
options.work_dir |
string | Diretorio de trabalho |
options.env |
table | Variaveis de ambiente |
options.pty |
table | Aloca um pseudo-terminal para o processo filho |
options.process_group |
boolean | Inicia o processo filho em seu próprio grupo de processos para que os sinais também alcancem os descendentes; não suportado no Windows |
Retorna: Process, error
O processo é criado mas não iniciado.
Parsing do Comando
cmd é dividido em um executável e argumentos literais usando aspas no estilo shell: aspas simples e duplas agrupam uma palavra, e a barra invertida escapa o caractere seguinte. Não há shell, portanto não ocorre expansão de variáveis, globbing, pipes ou redirecionamento. Uma aspa não fechada retorna errors.INVALID.
-- Um argumento contendo um espaço, passado literalmente
local proc = executor:exec("grep 'hello world' notes.txt")
-- $HOME é passado como os quatro caracteres $HOME, sem expansão
local proc = executor:exec("echo $HOME")
Para usar recursos do shell, invoque um shell explicitamente:
local proc = executor:exec("/bin/sh -c 'ls *.log | wc -l'")
Opções de PTY
Alocar um PTY dá ao processo filho um terminal real: edição de linha, controle de jobs e programas de tela cheia funcionam como em um shell.
local proc = executor:exec("/bin/bash --noprofile --norc", {
pty = {width = 100, height = 30, term = "xterm-256color"},
})
| Campo | Tipo | Padrão | Descrição |
|---|---|---|---|
width |
number | 80 | Colunas iniciais do PTY, 1 a 65535 |
height |
number | 24 | Linhas iniciais do PTY, 1 a 65535 |
term |
string | nenhum | Valor de TERM do processo filho |
Largura vezes altura não pode exceder 262.144 células. Um processo com PTY mescla a saída do filho em um único stream de terminal; conduza-o com resize e attach_terminal em vez dos métodos de pipe stdin/stdout.
start / wait
Iniciar o processo e aguardar conclusao.
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 falhou com código de saida: " .. exit_code })
end
stdout_stream / stderr_stream
Obter streams para ler saida do processo.
local proc = executor:exec("./process-data.sh")
local stdout = proc:stdout_stream()
local stderr = proc:stderr_stream()
proc:start()
-- Ler todo 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)
-- Verificar erros
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
Escrever dados para stdin do processo.
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()
Cada chamada escreve os bytes fornecidos e retorna. Chame close_stdin() quando o processo filho precisar ver EOF:
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() é idempotente. As escritas posteriores falham porque o lado de entrada está fechado. Processos baseados em PTY não expõem essa operação de pipe.
done
Use done() para observar a saída sem consumir o handle do processo:
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
O canal retornado entrega um registro de saída e então é fechado. Chamadas repetidas retornam o mesmo canal. O registro contém code, signal opcional e error somente quando o runtime não conseguiu observar a saída. Uma saída por sinal usa 128 + signal como seu código. Diferentemente de wait(), done() mantém o handle utilizável, então streams, signal() e close() continuam disponíveis. wait() após a entrega retorna o código registrado.
signal / close
Enviar sinais ou liberar o processo.
local proc = executor:exec("./long-running-server.sh")
proc:start()
-- ... depois, precisa parar ...
-- Envia SIGTERM e libera o handle
proc:close()
-- Envia SIGKILL e libera o handle
proc:close(true)
-- Ou envia um sinal específico e mantém o handle
local SIGINT = 2
proc:signal(SIGINT)
close(force?) sinaliza um filho iniciado com SIGTERM, ou SIGKILL quando force é verdadeiro, e então o coleta em segundo plano, de modo que a chamada não bloqueia. Um filho que ainda executa após um período de carência é morto para que a coleta sempre se complete. Um handle não iniciado é simplesmente invalidado, e fechar duas vezes não é um erro. Quando process_group está habilitado, os sinais são enviados ao grupo e continuam alcançando os descendentes depois que o líder termina.
Streams adquiridos antes da coleta permanecem legíveis até que seu último escritor seja fechado, inclusive um descendente que herdou o pipe. Depois de close(), os métodos do processo reportam process closed; use done() quando a saída importar e o handle precisar continuar utilizável.
resize
Redimensiona o PTY de um processo com PTY. Um processo baseado em pipe retorna um erro.
local ok, err = proc:resize(120, 40)
| Parâmetro | Tipo | Descrição |
|---|---|---|
width |
number | Colunas, 1 a 65535 |
height |
number | Linhas, 1 a 65535 |
Retorna: boolean, error
Use-o para definir a geometria inicial antes de entregar o processo a uma sessão de terminal. Uma vez que a sessão é dona do processo, envie a ela um evento resize.
attach_terminal
Anexa um processo com PTY não iniciado ao terminal do processo chamador e retorna uma 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())
Retorna: TerminalSession, error
A chamada consome o processo: a sessão torna-se a única dona do seu ciclo de vida e o handle original não pode mais ser usado. A sessão abre uma surface na porta de terminal atual e é dona da emulação de PTY, codificação de entrada, redimensionamento, término gracioso e forçado, e coleta. Ela precisa de uma porta de terminal — um processo em terminal host, ou um processo criado com uma concessão de viewport — e falha quando a porta não tem controlador de entrada ou já tem uma surface aberta.
TerminalSession
| Método | Retorna | Descrição |
|---|---|---|
send(event) |
boolean, error |
Encaminha um evento TTY canônico ao processo filho |
done() |
channel | Canal que dispara uma vez quando o filho termina |
status() |
string, error |
"running" ou "done", com o erro de falha quando falhou |
close() |
boolean, error |
Solicita o término de um filho em execução |
send aceita os registros de tecla, mouse, resize, foco e paste descritos em TTY. Enviar após o filho ter terminado retorna um erro.
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())
Permissões
Operações de exec estao sujeitas a avaliação de política de segurança.
| Ação | Recurso | Descrição |
|---|---|---|
exec.get |
ID do Executor | Obter um recurso de executor |
exec.run |
Comando | Executar um comando especifico |
exec.run é avaliado contra a string de comando bruta, com as opções solicitadas como metadados:
| Chave | Tipo | Descrição |
|---|---|---|
work_dir |
string | Diretório de trabalho solicitado, vazio quando não definido |
env_names |
string[] | Nomes das variáveis de ambiente passadas, ordenados; os valores não são expostos |
pty.requested |
boolean | Se um PTY foi solicitado |
pty.width |
number | Colunas do PTY resolvidas, presente quando solicitado |
pty.height |
number | Linhas do PTY resolvidas, presente quando solicitado |
pty.term |
string | Valor de TERM solicitado, presente quando solicitado |
Uma política pode, portanto, permitir comandos simples enquanto restringe aqueles que pedem um terminal ou um diretório de trabalho específico.
Erros
| Condição | Tipo | Retentável |
|---|---|---|
| ID inválido | errors.INVALID |
não |
| Permissão negada | errors.INVALID |
não |
| Processo fechado | errors.INVALID |
não |
| Processo não iniciado | errors.INVALID |
não |
| Ja iniciado | errors.INVALID |
não |
| Aspa não fechada no comando | errors.INVALID |
não |
| Nenhum PTY no processo | errors.INVALID |
não |
| Porta de terminal indisponível | errors.UNAVAILABLE |
não |
Veja Error Handling para trabalhar com erros.
Veja Também
- Executor — configuração do executor
- TTY — eventos de terminal, surfaces e viewports
- UI de Terminal — um shell que hospeda um filho com PTY em um viewport