Bash
Run shell scripts from an Earl template.
The Bash protocol runs a shell script and returns its output. Earl blocks all private and loopback IP ranges at the SSRF layer regardless of sandbox settings — network = true only enables outbound connections to public addresses.
A complete example
version = 1
provider = "local"
categories = ["files"]
command "count_lines" {
title = "Count lines"
summary = "Count lines in a file matching a pattern"
description = "Runs grep to count lines matching the given pattern in a file. Returns the count."
annotations {
mode = "read"
}
param "path" {
type = "string"
required = true
description = "Path to the file"
}
param "pattern" {
type = "string"
required = true
description = "Pattern to grep for"
}
operation {
protocol = "bash"
bash {
script = <<-SH
grep -c "$EARL_PATTERN" "$EARL_PATH" || echo 0
SH
env = {
EARL_PATH = "{{ args.path }}"
EARL_PATTERN = "{{ args.pattern }}"
}
sandbox {
network = false
max_time_ms = 5000
max_output_bytes = 65536
}
}
}
result {
decode = "text"
output = "{{ result | trim }} matching lines"
}
}Run it:
earl call local.count_lines --path /var/log/syslog --pattern ERRORWalk-through
bash block
bash {
script = <<-SH
grep -c "$EARL_PATTERN" "$EARL_PATH" || echo 0
SH
env = {
EARL_PATH = "{{ args.path }}"
EARL_PATTERN = "{{ args.pattern }}"
}
...
}script is the shell script. The heredoc syntax (<<-SH ... SH) keeps multi-line scripts readable without escaping. Args are passed through env and referenced in the script as "$VAR_NAME". When a variable is expanded with double quotes, the shell treats its value as a literal string — shell operators, command substitution, and word splitting in the value are not interpreted. Rendering args directly into the script string ({{ args.path }}) instead of through env vars allows values containing ;, $(), or backticks to inject additional shell commands.
sandbox block
sandbox {
network = false
max_time_ms = 5000
max_output_bytes = 65536
}network = false blocks outbound network calls from the script. max_time_ms kills the script if it runs past the limit. max_output_bytes caps the combined size of stdout and stderr.
Two more fields are available: max_memory_bytes limits RSS, and max_cpu_time_ms limits total CPU time rather than wall time.
Omitting the sandbox block entirely means no limits apply. Always set at least max_time_ms.
Note that even with network = true, Earl blocks all private and loopback IP ranges (127.0.0.0/8, 10.0.0.0/8, and similar) at the SSRF layer. There is no config option to bypass this.
result
result {
decode = "text"
output = "{{ result | trim }} matching lines"
}Bash output is plain text. decode = "text" gives the full stdout string as result. Use the trim filter to strip trailing newlines before formatting.
Env vars and writable_paths
Pass string args through env and reference them as "$VAR_NAME" in the script. This prevents shell injection: a value like . ; rm -rf . is passed as a literal string to the command rather than being interpreted by the shell. Rendering args directly into the script ({{ args.path }}) does not have this property — values containing $(), backticks, or ; execute as shell code.
command "extract_json_field" {
title = "Extract JSON field"
summary = "Extract a field from a JSON file using jq"
description = "Runs jq on a JSON file and writes the result to an output file."
annotations {
mode = "write"
}
param "input_path" {
type = "string"
required = true
description = "Path to the input JSON file"
}
param "filter" {
type = "string"
required = true
description = "jq filter expression (e.g. '.users[].name')"
}
param "output_path" {
type = "string"
required = true
description = "Path to write the output"
}
operation {
protocol = "bash"
bash {
script = <<-SH
jq -r "$JQ_FILTER" "$INPUT" > "$OUTPUT"
echo "Written to $OUTPUT"
SH
env = {
INPUT = "{{ args.input_path }}"
OUTPUT = "{{ args.output_path }}"
JQ_FILTER = "{{ args.filter }}"
}
sandbox {
network = false
max_time_ms = 10000
writable_paths = ["{{ args.output_path }}"]
}
}
}
result {
decode = "text"
output = "{{ result | trim }}"
}
}writable_paths grants write access to specific filesystem paths. The script can write there but nowhere else. Paths support Jinja expressions, so you can derive them from params at render time.
To switch between environments in scripts, see Environments.
For naming conventions and other patterns that apply across all protocols, see Best Practices.
For streaming stdout line-by-line, see Streaming — Bash streaming. For the full field reference, see Template Schema — Bash.