JustBash.Interpreter.Executor.Redirection (JustBash v0.4.0)

View Source

Handles file redirections for bash commands.

Supports:

  • Output redirection: >, >>
  • Error redirection: 2>, 2>>
  • Input redirection: <
  • Here-strings: <<<
  • Combined redirection: &>
  • Stream duplication: >&, 2>&1, 1>&2
  • /dev/null handling

Summary

Types

A redirection whose target has already been expanded, resolved, and opened by preflight/2.

Functions

Apply preflighted redirections to the result, writing each stream to the target preflight/2 already opened.

Extract heredoc, here-string or < file content as stdin.

Expand, resolve, and open every redirection target, left to right, before the command body runs.

Types

prepared()

@type prepared() :: {redir_type(), String.t() | nil}

A redirection whose target has already been expanded, resolved, and opened by preflight/2.

The path is nil for redirections that touch no file (/dev/null, stream duplication, <). Carrying the resolved path forward is what keeps a target containing a command substitution from being expanded a second time when the output is finally written.

redir_type()

@type redir_type() ::
  :stdout_dev_null
  | :stderr_dev_null
  | :combined_dev_null
  | :stdout_write
  | :stdout_append
  | :stderr_write
  | :stderr_append
  | :stdout_to_stderr
  | :stderr_to_stdout
  | :combined_write
  | :combined_append
  | :stdin_read
  | :close_fd
  | :noop

result()

@type result() :: %{
  stdout: String.t(),
  stderr: String.t(),
  exit_code: non_neg_integer()
}

Functions

apply_redirections(result, bash, list)

@spec apply_redirections(result(), JustBash.t(), [prepared()]) ::
  {result(), JustBash.t()}

Apply preflighted redirections to the result, writing each stream to the target preflight/2 already opened.

extract_heredoc_stdin(bash, redirections)

@spec extract_heredoc_stdin(JustBash.t(), [JustBash.AST.Redirection.t()]) ::
  {:ok, String.t() | nil, [JustBash.AST.Redirection.t()]} | {:error, result()}

Extract heredoc, here-string or < file content as stdin.

Returns {:ok, stdin_content, non_heredoc_redirections}, or {:error, result} when a < file target cannot be opened. The shell opens that target itself, so — like the write side in preflight/2 — a failure is the shell's to report and the command never runs.

preflight(bash, redirections)

@spec preflight(JustBash.t(), [JustBash.AST.Redirection.t()]) ::
  {:ok, [prepared()], JustBash.t()} | {:error, result(), JustBash.t()}

Expand, resolve, and open every redirection target, left to right, before the command body runs.

bash opens redirect targets before forking the command, so a target it cannot open means the command never runs — none of its side effects happen. On {:error, result, bash} the caller must return result without executing the body; on {:ok, prepared, bash} it runs the body and hands prepared to apply_redirections/3.

Opening follows the open/2 flags bash uses: >, 2> and &> create or truncate (O_CREAT | O_TRUNC), >> and &>> create only if missing (O_CREAT | O_APPEND). Redirections that touch no file — the special-file table's null device (/dev/null, /dev/./null), >&, < — are classified and passed through untouched.

Targets to the left of a failing one are still created or truncated, and targets to its right are never expanded, so a command substitution in one of them does not run. Both match bash.