Define Elixir functions callable from Bash scripts.
This module provides the defbash macro for creating functions that can be
invoked from bash scripts using the namespace.function syntax.
Usage
defmodule MyApp.BashAPI do
use Bash.Interop, namespace: "myapp"
defbash greet(args, _state) do
case args do
[name | _] ->
Bash.puts("Hello #{name}!\n")
:ok
[] ->
{:error, "usage: myapp.greet NAME"}
end
end
endFunctions are loaded into a session with Bash.Session.load_api/2:
{:ok, session} = Bash.Session.new()
Bash.Session.load_api(session, MyApp.BashAPI)Then callable from Bash as myapp.greet "World".
I/O Functions
Inside defbash functions, use the Bash module for I/O:
Bash.puts(message)- Write raw data to stdout (no newline added)Bash.puts(:stderr, message)- Write raw data to stderr (no newline added)Bash.stream(:stdin)- Get stdin as a lazyStreamBash.stream(:stdout, enumerable)- Stream each element to stdoutBash.stream(:stderr, enumerable)- Stream each element to stderrBash.get_state()- Get the current session state mapBash.update_state(updates)- Accumulate state update deltas
Return Values
defbash functions must return one of:
Success
:ok- Exit code 0, no additional output{:ok, message}- Exit code 0,message(binary) written to stdout{:ok, stream}- Exit code 0, lazy stream consumed to stdout{:ok, exit_code}- Exit withexit_code(integer), no additional output
Error
{:error, exit_code}- Exit withexit_code(integer), no output{:error, message}- Exit code 1,message(binary) written to stderr{:error, stream}- Exit code 1, lazy stream consumed to stderr
Loop Control
Only valid when the function is called inside a for, while, or until loop:
:continue- Skip to the next loop iteration:break- Break out of the enclosing loop
State Updates
Use Bash.update_state/1 to request changes to session state. Updates are
accumulated as deltas and applied after execution completes. Common keys:
:variables- Map of variable name to string orBash.Variable(merged, strings auto-wrapped as exported):working_dir- New working directory (replaced):options- Shell options map (merged)
Examples
Simple output
defbash hello(_args, _state) do
Bash.puts("hello world\n")
:ok
endReturn-based output
defbash version(_args, _state) do
{:ok, "1.0.0\n"}
endError handling
defbash divide(args, _state) do
case args do
[a, b] ->
b = String.to_integer(b)
if b == 0 do
{:error, "divide: division by zero\n"}
else
{:ok, "#{div(String.to_integer(a), b)}\n"}
end
_ ->
{:error, "usage: math.divide A B\n"}
end
endStreaming stdin
defbash upcase(_args, _state) do
Bash.stream(:stdin)
|> Stream.each(fn chunk ->
Bash.puts(String.upcase(chunk))
end)
|> Stream.run()
:ok
endStreaming output
defbash count(_args, _state) do
{:ok, Stream.map(1..5, &"#{&1}\n")}
endCustom exit code
defbash check(args, _state) do
if File.exists?(List.first(args, "")) do
:ok
else
{:ok, 1}
end
endState updates
defbash set_var(args, _state) do
case args do
[name, value] ->
Bash.update_state(%{variables: %{name => Bash.Variable.new(value)}})
:ok
_ ->
{:error, "usage: myapp.set_var NAME VALUE\n"}
end
endLoop control
# In a bash loop: for i in 1 2 3; do myapp.skip_even "$i"; done
defbash skip_even(args, _state) do
n = args |> List.first() |> String.to_integer()
if rem(n, 2) == 0 do
:continue
else
Bash.puts("#{n}\n")
:ok
end
end
Summary
Functions
Sets up a module to define bash-callable functions.
Options
:namespace- Required. The namespace prefix for bash calls.:on_define- Optional. A callback function invoked at compile time when eachdefbashfunction is defined. Receives(function_name, module)and should return a metadata map ornil. This allows external modules to annotate functions using module attributes.
Example
defmodule MyApp.BashAPI do
use Bash.Interop, namespace: "myapp"
endExample with on_define callback
defmodule MyApp.RemoteBashAPI do
use Bash.Interop,
namespace: "remote",
on_define: fn name, module ->
execute_on = Module.get_attribute(module, :execute_on) || :guest
Module.delete_attribute(module, :execute_on)
%{execute_on: execute_on}
end
@execute_on :server
defbash server_func(args, _state), do: {:ok, "runs on server"}
defbash guest_func(args, _state), do: {:ok, "runs on guest"}
end
Define a bash-callable function.
The function receives two arguments:
args- List of string arguments passed from bashstate- The current session state map
I/O
Inside the function body, use the Bash module for I/O:
Bash.puts(message)- Write raw data to stdoutBash.puts(:stderr, message)- Write raw data to stderrBash.stream(:stdin)- Get stdin as a lazyStreamBash.stream(:stdout, enumerable)- Stream each element to stdoutBash.stream(:stderr, enumerable)- Stream each element to stderrBash.get_state()- Get current session stateBash.update_state(updates)- Accumulate state update deltas
Return Values
Success
:ok- Exit code 0, no additional output{:ok, binary}- Exit code 0, binary written to stdout{:ok, stream}- Exit code 0, lazy stream consumed to stdout{:ok, exit_code}- Exit with integer code, no additional output
Error
{:error, exit_code}- Exit with integer code, no output{:error, binary}- Exit code 1, binary written to stderr{:error, stream}- Exit code 1, lazy stream consumed to stderr
Loop Control
:continue- Skip to next loop iteration (only valid inside loops):break- Break out of enclosing loop (only valid inside loops)
Examples
defbash greet(args, _state) do
case args do
[name | _] ->
Bash.puts("Hello #{name}!\n")
:ok
[] ->
{:error, "usage: myapp.greet NAME\n"}
end
end
defbash upcase(_args, _state) do
Bash.stream(:stdin)
|> Stream.each(fn chunk ->
Bash.puts(String.upcase(chunk))
end)
|> Stream.run()
:ok
end
defbash count(_args, _state) do
{:ok, Stream.map(1..5, &"#{&1}\n")}
end