NetRunner (NetRunner v1.2.1)

Copy Markdown View Source

Safe OS process execution for Elixir.

Combines NIF-based async I/O with a persistent shepherd binary to guarantee zero zombie processes, even under BEAM SIGKILL.

Quick start

# Simple command execution
{output, 0} = NetRunner.run(~w(echo hello))

# Streaming with input
NetRunner.stream!(~w(cat), input: "hello world")
|> Enum.to_list()
# => ["hello world"]

# Piping data through a command
NetRunner.stream!(~w(wc -c), input: "hello")
|> Enum.join()
# => "       5\n"

Summary

Functions

Runs a command and collects all output.

Like stream!/2 but returns {:ok, stream} or {:error, reason}.

Creates a stream for incremental I/O with the command.

Functions

run(command, opts \\ [])

@spec run(
  NetRunner.Command.t() | [String.t()],
  keyword()
) :: {binary(), non_neg_integer()} | {:error, term()}

Runs a command and collects all output.

Accepts either a command list [executable | args] or a %NetRunner.Command{} struct.

Returns {output, exit_status} where output is the concatenated stdout.

Options

  • :stderr - :consume (default, drained internally so the child never blocks on a full stderr pipe) or :disabled
  • :input - data to write to stdin (binary or enumerable)
  • :timeout - maximum wall-clock time in milliseconds. Sends SIGTERM then SIGKILL on timeout. Returns {:error, :timeout} instead of {output, exit_status}.
  • :max_output_size - maximum bytes to collect from stdout. Kills the process and returns {:error, {:max_output_exceeded, partial_output}} if exceeded.

Examples

{output, 0} = NetRunner.run(~w(echo hello))
{"hello\n", 0} = {output, 0}

{output, 0} = NetRunner.run(~w(cat), input: "from stdin")

{:error, :timeout} = NetRunner.run(~w(sleep 100), timeout: 100)

{:error, {:max_output_exceeded, _partial}} =
  NetRunner.run(["sh", "-c", "yes"], max_output_size: 1000)

# With a Command struct:
cmd = NetRunner.Command.new("echo", ["hello"], timeout: 5_000)
{output, 0} = NetRunner.run(cmd)

stream(command, opts \\ [])

@spec stream(
  NetRunner.Command.t() | [String.t()],
  keyword()
) :: {:ok, Enumerable.t()} | {:error, term()}

Like stream!/2 but returns {:ok, stream} or {:error, reason}.

Accepts either a command list [executable | args] or a %NetRunner.Command{} struct.

stream!(command, opts \\ [])

@spec stream!(
  NetRunner.Command.t() | [String.t()],
  keyword()
) :: Enumerable.t()

Creates a stream for incremental I/O with the command.

Accepts either a command list [executable | args] or a %NetRunner.Command{} struct.

Returns a Stream that yields stdout binary chunks. Raises on process start failure.

Options

  • :input - data to write to stdin (binary, list, or Stream)
  • :stderr - :consume (default) or :disabled

Examples

# Stream through a command
NetRunner.stream!(~w(sort))
|> Enum.to_list()

# With input
NetRunner.stream!(~w(tr a-z A-Z), input: "hello")
|> Enum.join()
# => "HELLO"

# With a Command struct:
cmd = NetRunner.Command.new("cat", [], input: "hello")
NetRunner.stream!(cmd) |> Enum.to_list()