Session GenServer for maintaining Bash execution context.
Each session maintains its own environment variables, working directory, and I/O context for executing Bash commands.
| Variable | Description |
|---|---|
BASH_VERSION | "5.3" Version information for this Bash. |
CDPATH | A colon-separated list of directories to search for directories given as arguments to cd. |
GLOBIGNORE | A colon-separated list of patterns describing filenames to be ignored by pathname expansion. |
HISTFILE | The name of the file where your command history is stored. |
HISTFILESIZE | The maximum number of lines this file can contain. |
HISTSIZE | The maximum number of history lines that a running shell can access. |
HOME | The complete pathname to your login directory. |
HOSTNAME | The name of the current host. |
HOSTTYPE | The type of CPU this version of Bash is running under. |
IGNOREEOF | Controls the action of the shell on receipt of an EOF character as the sole input. If set, then the value of it is the number of EOF characters that can be seen in a row on an empty line before the shell will exit (default 10). When unset, EOF signifies the end of input. |
MACHTYPE | A string describing the current system Bash is running on. |
MAILCHECK | How often, in seconds, Bash checks for new mail. |
MAILPATH | (Unsupported) A colon-separated list of filenames which Bash checks for new mail. |
OSTYPE | The version of Unix this version of Bash is running on. |
PATH | A colon-separated list of directories to search when looking for commands. |
PROMPT_COMMAND | (Unsupported) A command to be executed before the printing of each primary prompt. |
PS1 | (Unsupported) The primary prompt string. |
PS2 | (Unsupported) The secondary prompt string. |
PWD | The full pathname of the current directory. |
SHELLOPTS | A colon-separated list of enabled shell options. |
TERM | (Always set to "dumb") The name of the current terminal type. |
TIMEFORMAT | The output format for timing statistics displayed by the time reserved word. |
auto_resume | (Unsupported) Non-null means a command word appearing on a line by itself is first looked for in the list of currently stopped jobs. If found there, that job is foregrounded. A value of exact means that the command word must exactly match a command in the list of stopped jobs. A value of substring means that the command word must match a substring of the job. Any other value means that the command must be a prefix of a stopped job. |
histchars | (Unsupported) Characters controlling history expansion and quick substitution. The first character is the history substitution character, usually !. The second is the quick substitution character, usually ^. The third is the history comment character, usually #. |
| HISTIGNORE | A colon-separated list of patterns used to decide which commands should be saved on the history
list. |
Summary
Functions
Send job to background (resume if stopped).
Begin a new command execution with fresh StringIO streams.
Changes the working directory for the session.
Close a file descriptor.
End the current execution and move it to completed executions.
Executes a command AST within this session synchronously.
Executes a command AST within this session asynchronously.
Get a specific execution by index.
Clear the accumulated output and return what was collected.
Bring job to foreground.
Gets all environment variables from the session.
Get the command history for this session.
Gets the current working directory for the session.
Gets an environment variable from the session.
Get a specific job by number.
Get accumulated output from the session's output collector.
Get the session state (for builtins that need direct access).
Gets a variable value from the session, with optional index/key for arrays.
Read a line from stdin (convenience wrapper for read/3).
Lists all running sessions.
List loaded API namespaces.
Get all jobs for this session.
Load an Elixir API module into a session.
Creates a new session with default environment.
Creates a child session that inherits state from a parent.
Open a file descriptor for reading or writing.
Open a StringIO device for stdin from a string.
Wire the previous execution's stdout to the current stdin for pipeline stages.
Get and clear completed jobs for notification display.
Write a line to stdout (convenience wrapper for write/3).
Read from an input source.
Sets an environment variable in the session.
Send signal to job.
Start a background job and return its job number and OS PID.
Get the merged stderr content from all completed executions.
Get the merged stdout content from all completed executions.
Stops a session and its job supervisor.
Wait for job(s) to complete.
Write to an output destination.
Types
@type t() :: %Bash.Session{ aliases: %{required(String.t()) => String.t()}, call_stack: [ %{ line_number: pos_integer(), function_name: String.t(), source_file: String.t() } ], call_timeout: timeout(), command_history: [Bash.CommandResult.t()], completed_jobs: [Bash.Job.t()], current: Bash.Execution.t() | nil, current_function_name: term(), current_job: pos_integer() | nil, dir_stack: [String.t()], elixir_modules: %{required(String.t()) => module()}, executions: [Bash.Execution.t()], file_descriptors: %{ required(non_neg_integer()) => pid() | {:coproc, pid(), :read | :write} }, functions: %{required(String.t()) => Bash.AST.Function.t()}, hash: %{required(String.t()) => {pos_integer(), String.t()}}, id: String.t(), in_function: boolean(), in_loop: boolean(), is_pipeline_tail: boolean(), job_supervisor: pid() | nil, jobs: %{required(pos_integer()) => pid()}, next_job_number: pos_integer(), options: %{required(String.t()) => boolean()}, output_collector: pid() | nil, pipe_stdin: term(), positional_params: [[String.t()]], previous_job: pos_integer() | nil, signal_jobs_fn: term(), special_vars: %{required(String.t()) => integer() | String.t() | nil}, start_background_job_fn: term(), start_runtime_ms: term(), stderr: pid() | nil, stderr_sink: Bash.Sink.t() | nil, stdin: pid() | nil, stdin_device: pid() | nil, stdout: pid() | nil, stdout_sink: Bash.Sink.t() | nil, traps: %{required(String.t()) => String.t() | :ignore}, variables: %{required(String.t()) => Bash.Variable.t()}, working_dir: String.t() }
Functions
@spec background_job(pid(), pos_integer() | nil) :: :ok | {:error, term()}
Send job to background (resume if stopped).
Begin a new command execution with fresh StringIO streams.
Creates a new Execution struct with separate stdout/stderr streams and sets it as the current execution.
Options
:pipeline_tail- Whether this command is at the end of a pipeline (default: true). Only pipeline tail commands forward to user sinks.
Examples
session = Session.begin_execution(session, "echo hello")
session = Session.begin_execution(session, "cat", pipeline_tail: false)
Changes the working directory for the session.
@spec close_fd(t(), non_neg_integer()) :: t()
Close a file descriptor.
Examples
session = Session.close_fd(session, 3)
End the current execution and move it to completed executions.
Marks the execution with the given exit code and timestamp, then appends it to the executions list.
Options
:exit_code- The exit code for the execution (default: 0)
Examples
session = Session.end_execution(session, exit_code: 0)
session = Session.end_execution(session, exit_code: 1)
Executes a command AST within this session synchronously.
Blocks until the command completes and returns the result.
Options
:on_output- Callback function for streaming output. When provided, output is streamed to the callback as it arrives instead of being accumulated in the result. The callback receives{:stdout, binary}or{:stderr, binary}tuples.
Examples
# Standard execution (accumulates output)
{:ok, session} = Session.new()
{:ok, result} = Session.execute(session, ast)
# Streaming execution (output flows to callback)
{:ok, session} = Session.new()
{:ok, result} = Session.execute(session, ast, on_output: fn
{:stdout, data} -> IO.write(data)
{:stderr, data} -> IO.write(:stderr, data)
end)
Executes a command AST within this session asynchronously.
Returns immediately without waiting for the command to complete. The result will be stored in the session's command history.
Examples
{:ok, session} = Session.new()
:ok = Session.execute_async(session, ast)
# Command executes in background
@spec execution(t(), non_neg_integer()) :: Bash.Execution.t() | nil
Get a specific execution by index.
Examples
exec = Session.execution(session, 0)
Execution.stdout_contents(exec)
Clear the accumulated output and return what was collected.
Useful for tests that want to run multiple commands and check output after each.
@spec foreground_job(pid(), pos_integer() | nil) :: {:ok, Bash.CommandResult.t()} | {:error, term()}
Bring job to foreground.
Blocks until the job completes and returns a CommandResult.
Gets all environment variables from the session.
@spec get_command_history(pid()) :: [Bash.CommandResult.t()]
Get the command history for this session.
Gets the current working directory for the session.
Gets an environment variable from the session.
@spec get_job(pid(), pos_integer()) :: {:ok, Bash.Job.t()} | {:error, :not_found}
Get a specific job by number.
Get accumulated output from the session's output collector.
Returns {stdout, stderr} tuple with all output captured during execution.
This is the primary way to retrieve output in tests.
Examples
{:ok, session} = Session.new()
{:ok, _, _} = Bash.run(~b"echo hello", session)
{stdout, stderr} = Session.get_output(session)
assert stdout =~ "hello"
Get the session state (for builtins that need direct access).
Gets a variable value from the session, with optional index/key for arrays.
Retrieves the session state and delegates to Variable.get/2 or Variable.get/3.
Examples
Session.get_var(session, "myvar")
Session.get_var(session, "myarray", 0)
Session.get_var(session, "myassoc", "key")
Read a line from stdin (convenience wrapper for read/3).
Options
:source- Source to read from (default: :stdin):delimiter- Line delimiter (default: "\n")
Examples
{:ok, line} = Session.gets(session)
{:ok, line} = Session.gets(session, source: {:fd, 3})
Lists all running sessions.
Returns a list of tuples containing the session ID and pid.
Options
:registry- The registry to query (default:Bash.SessionRegistry)
Examples
iex> {:ok, session} = Bash.Session.new()
iex> sessions = Bash.Session.list()
iex> Enum.any?(sessions, fn {_id, pid} -> pid == session end)
true
List loaded API namespaces.
@spec list_jobs(pid()) :: [Bash.Job.t()]
Get all jobs for this session.
Load an Elixir API module into a session.
The module must use Bash.Interop and define a namespace.
Once loaded, functions defined with defbash become callable from
bash scripts as namespace.function_name.
Examples
# Load into a running session (recommended)
{:ok, session} = Session.new()
:ok = Session.load_api(session, MyApp.BashAPI)
# Or load at session creation
{:ok, session} = Session.new(apis: [MyApp.BashAPI])
# Now myapp.* functions are available in bash scripts
Creates a new session with default environment.
Creates a child session that inherits state from a parent.
The child session inherits (per bash behavior):
- Environment variables
- Working directory
- Functions
- Shell options
The child session does NOT inherit (per bash behavior):
- Aliases
- Hash table (command path cache)
The child session gets its own:
- Job supervisor and job table
- Session ID
This is used for subshell execution where changes to env/cwd should not propagate back to the parent.
Open a file descriptor for reading or writing.
Examples
session = Session.open_fd(session, 3, "/path/to/file", [:read])
session = Session.open_fd(session, 4, "/path/to/output", [:write])
Open a StringIO device for stdin from a string.
Useful for providing initial input to a session or pipeline.
Examples
session = Session.open_stdin(session, "line1\nline2\n")
Wire the previous execution's stdout to the current stdin for pipeline stages.
Takes the stdout content from the last completed execution and creates a new StringIO device for reading as the next command's stdin.
Examples
# After cmd1 completes:
session = Session.pipe_forward(session)
# Now stdin reads from cmd1's stdout
@spec pop_completed_jobs(pid()) :: [Bash.Job.t()]
Get and clear completed jobs for notification display.
Write a line to stdout (convenience wrapper for write/3).
Appends a newline to the data.
Examples
session = Session.puts(session, "hello") # writes "hello\n"
@spec read(t(), :stdin | {:fd, non_neg_integer()}, :line | :all | non_neg_integer()) :: {:ok, String.t()} | :eof | {:error, term()}
Read from an input source.
Sources
:stdin- Read from session's stdin{:fd, n}- Read from file descriptor n
Modes
:line- Read a single line (default):all- Read all available contentnwhen is_integer(n) - Read n bytes
Examples
{:ok, line} = Session.read(session, :stdin, :line)
{:ok, all} = Session.read(session, :stdin, :all)
{:ok, chunk} = Session.read(session, {:fd, 3}, 1024)
Sets an environment variable in the session.
@spec signal_job(pid(), pos_integer(), atom() | integer()) :: :ok | {:error, term()}
Send signal to job.
@spec start_background_job( pid(), keyword() ) :: {:ok, job_number :: pos_integer(), os_pid :: pos_integer() | nil} | {:error, term()}
Start a background job and return its job number and OS PID.
Options
:command- Command name to execute (required):args- List of arguments (default: [])
@spec stderr( t(), keyword() ) :: Enumerable.t() | String.t()
Get the merged stderr content from all completed executions.
Options
:index- Get stderr from a specific execution by index
Examples
# All stderr as a stream
Session.stderr(session) |> Enum.to_list()
# Specific execution's stderr
Session.stderr(session, index: 0)
@spec stdout( t(), keyword() ) :: Enumerable.t() | String.t()
Get the merged stdout content from all completed executions.
Options
:index- Get stdout from a specific execution by index
Examples
# All stdout as a stream
Session.stdout(session) |> Enum.to_list()
# Specific execution's stdout
Session.stdout(session, index: 0)
@spec stop(pid()) :: :ok
Stops a session and its job supervisor.
@spec wait_for_jobs(pid(), [pos_integer()] | nil) :: {:ok, [integer()]} | {:error, term()}
Wait for job(s) to complete.
@spec write(t(), :stdout | :stderr | {:fd, non_neg_integer()}, iodata()) :: t()
Write to an output destination.
Writes to the current execution's StringIO stream. If the session is at the pipeline tail and has user-provided sinks, also forwards to those.
Destinations
:stdout- Write to stdout:stderr- Write to stderr{:fd, n}- Write to file descriptor n
Examples
session = Session.write(session, :stdout, "hello\n")
session = Session.write(session, :stderr, "error message\n")
session = Session.write(session, {:fd, 3}, data)