# Exec
_Path: en/lua/dynamic/exec_
## Table of Contents
- Command Execution
## Content
# Command Execution
Execute external commands and shell scripts with full control over I/O streams.
For executor configuration, see [Executor](system/exec.md).
## Loading
```lua
local exec = require("exec")
```
## Acquiring an Executor
Get a process executor resource by ID:
```lua
local executor, err = exec.get("app:exec")
if err then
return nil, err
end
-- Use executor
local proc = executor:exec("ls -la")
-- ...
-- Release when done
executor:release()
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `id` | string | Resource ID |
**Returns:** `Executor, error`
## Creating a Process
Create a new process with the specified command:
```lua
-- Simple command
local proc, err = executor:exec("echo 'Hello, World!'")
-- With working directory
local proc = executor:exec("npm install", {
work_dir = "/app/project"
})
-- With environment variables
local proc = executor:exec("python script.py", {
work_dir = "/scripts",
env = {
PYTHONPATH = "/app/lib",
DEBUG = "true",
API_KEY = api_key
}
})
-- Run shell script
local proc = executor:exec("./deploy.sh production", {
work_dir = "/app/scripts",
env = {
DEPLOY_ENV = "production"
}
})
```
| Parameter | Type | Description |
|-----------|------|-------------|
| `cmd` | string | Command to execute |
| `options.work_dir` | string | Working directory |
| `options.env` | table | Environment variables |
**Returns:** `Process, error`
## start / wait
Start the process and wait for completion.
```lua
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("INTERNAL", "Build failed with exit code: " .. exit_code)
end
```
## stdout_stream / stderr_stream
Get streams to read process output.
```lua
local proc = executor:exec("./process-data.sh")
local stdout = proc:stdout_stream()
local stderr = proc:stderr_stream()
proc:start()
-- Read all 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)
-- Check for errors
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("INTERNAL", table.concat(err_output))
end
return result
```
## write_stdin
Write data to process stdin.
```lua
-- Pipe data to command
local proc = executor:exec("sort")
local stdout = proc:stdout_stream()
proc:start()
-- Write input
proc:write_stdin("banana\napple\ncherry\n")
proc:write_stdin("") -- Signal EOF
-- Read sorted output
local sorted = stdout:read()
print(sorted) -- "apple\nbanana\ncherry\n"
proc:wait()
stdout:close()
```
## signal / close
Send signals or close the process.
```lua
local proc = executor:exec("./long-running-server.sh")
proc:start()
-- ... later, need to stop it ...
-- Graceful shutdown (SIGTERM)
proc:close()
-- Or force kill (SIGKILL)
proc:close(true)
-- Or send specific signal
local SIGINT = 2
proc:signal(SIGINT)
```
## Permissions
Exec operations are subject to security policy evaluation.
| Action | Resource | Description |
|--------|----------|-------------|
| `exec.get` | Executor ID | Acquire an executor resource |
| `exec.run` | Command | Execute a specific command |
## Errors
| Condition | Kind | Retryable |
|-----------|------|-----------|
| Invalid ID | `errors.INVALID` | no |
| Permission denied | `errors.PERMISSION_DENIED` | no |
| Process closed | `errors.INVALID` | no |
| Process not started | `errors.INVALID` | no |
| Already started | `errors.INVALID` | no |
See [Error Handling](lua/core/errors.md) for working with errors.
## Navigation
Previous: Eval (lua/dynamic/eval)
Next: Expression (lua/dynamic/expression)