- Posted on
- • Artificial Intelligence
AI Agents with Bash
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
AI Agents with Bash: Turn Your Shell Into an Automation Copilot
If you’ve been watching the rise of “AI agents,” you might think you need a complex framework, a web UI, or a Kubernetes cluster to get value. Spoiler: you don’t. With Bash, curl, and jq, you can build a practical, auditable AI agent that plans and executes shell commands to help with everyday ops and data-wrangling tasks—right on your workstation.
This post shows you how to wire an LLM into your terminal safely, why it’s worth doing, and gives you a minimal, extensible agent you can run today.
Why build AI agents in Bash?
Ubiquity and composability: Bash is everywhere. Your tools—
grep,jq,find,systemctl—already solve 80% of problems. An LLM can plan how to chain them.Auditability and control: Everything is plain text. You can log prompts, outputs, and commands. Nothing runs unless you say so.
Privacy and cost: Use a local model via Ollama to keep data on-device. Or swap in a cloud endpoint if you need stronger reasoning.
Low friction: No new stack. A few functions and you’ve got a working agent loop you can customize.
What we’ll build
A tiny Bash agent that:
Takes a natural-language goal from you.
Asks an LLM to propose a shell command as JSON.
Shows you the plan, asks for confirmation, and executes in a guarded way.
Logs what happened for reproducibility and learning.
Prerequisites and installation
We’ll use curl and j q for HTTP and JSON parsing. Optional: ripgrep for fast text search.
Install these with your package manager:
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl jq ripgrep
- Fedora (dnf):
sudo dnf install -y curl jq ripgrep
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y curl jq ripgrep
You have two choices for the LLM:
1) Local (recommended): Install Ollama and pull a model (e.g., mistral or llama3)
curl -fsSL https://ollama.com/install.sh | sh
# In one terminal, start the server:
ollama serve
# In another terminal, pull a model:
ollama pull mistral
2) Cloud (OpenAI-compatible): Set your environment
export OPENAI_API_KEY="sk-..."
# Optional, for other providers that expose an OpenAI-compatible API:
# export OPENAI_BASE_URL="https://api.openai.com/v1"
# export OPENAI_MODEL="gpt-4o-mini" # or the model your provider supports
Note: Ollama isn’t generally available via apt/dnf/zypper. Use the vendor install script above.
Step 1 — A minimal LLM client in Bash
Create a new file called agent.sh:
#!/usr/bin/env bash
set -euo pipefail
require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing $1. Install it first."; exit 1; }; }
require_cmd curl
require_cmd jq
# Provider auto-detection
: "${OLLAMA_HOST:=http://127.0.0.1:11434}"
: "${OLLAMA_MODEL:=mistral}"
: "${OPENAI_BASE_URL:=https://api.openai.com/v1}"
: "${OPENAI_MODEL:=gpt-4o-mini}"
llm_chat() {
# $1 = system prompt, $2 = user prompt
if nc -z 127.0.0.1 11434 >/dev/null 2>&1; then
curl -fsS "$OLLAMA_HOST/api/chat" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg sys "$1" --arg usr "$2" --arg model "$OLLAMA_MODEL" \
'{model:$model, stream:false, messages:[{role:"system",content:$sys},{role:"user",content:$usr}], options:{temperature:0.2}}')" \
| jq -r '.message.content'
elif [[ -n "${OPENAI_API_KEY:-}" ]]; then
curl -fsS "$OPENAI_BASE_URL/chat/completions" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg sys "$1" --arg usr "$2" --arg model "$OPENAI_MODEL" \
'{model:$model, temperature:0.2, messages:[{role:"system",content:$sys},{role:"user",content:$usr}]}')" \
| jq -r '.choices[0].message.content'
else
echo "No LLM provider available. Start Ollama or set OPENAI_API_KEY." >&2
exit 1
fi
}
This function speaks either to a local Ollama server or a cloud endpoint that implements the OpenAI API.
Step 2 — Add a safe “tool use” loop
We’ll instruct the model to respond with strict JSON that includes a single safe command proposal. We then validate against an allowlist and ask you to confirm.
Append to agent.sh:
ALLOWLIST_REGEX='^(ls|cat|grep|rg|sed|awk|jq|curl|find|head|tail|sort|uniq|wc|du|df|systemctl|journalctl|tar|gzip|zcat|zgrep)\b'
SYSTEM_PROMPT='You are a Bash assistant.
Return ONLY a single-line JSON object with keys: thought, command, explain.
- command MUST be a safe, non-destructive CLI pipeline that runs in a typical Linux shell.
- Prefer read-only commands (grep, jq, find, systemctl status, journalctl, sort, uniq, wc, head, tail).
- Never write or remove files, never "sudo", never background jobs, never edit configs.
- Use POSIX/Bash-friendly one-liners.
- Do not include backticks or code fences.
- Keep command under 180 characters.'
propose_command() {
local goal="$1"
local resp
resp="$(llm_chat "$SYSTEM_PROMPT" "Goal: $goal")"
# Try to extract JSON even if the model adds text accidentally
local json
json="$(echo "$resp" | sed -n 's/.*{\(.*\)}/{\1}/p')"
if [[ -z "$json" ]]; then
echo "Model did not return JSON. Full response:" >&2
echo "$resp" >&2
exit 1
fi
echo "$json"
}
validate_and_confirm() {
local cmd="$1"
if ! [[ "$cmd" =~ $ALLOWLIST_REGEX ]]; then
echo "Blocked: command not in allowlist. Proposed: $cmd" >&2
exit 2
fi
echo "Plan: $cmd"
read -rp "Run this command? [y/N] " ans
[[ "${ans,,}" == "y" ]]
}
run_with_logging() {
local goal="$1" cmd="$2"
mkdir -p .agent_logs
local ts
ts="$(date +%Y%m%d-%H%M%S)"
echo "# $ts goal: $goal" | tee ".agent_logs/$ts.txt" >/dev/null
set +e
bash -o pipefail -c "$cmd" 2>&1 | tee -a ".agent_logs/$ts.txt"
local rc=$?
set -e
echo -e "$ts\t$rc\t$goal\t$cmd" >> ".agent_logs/history.tsv"
return $rc
}
main() {
if [[ $# -lt 1 ]]; then
echo "Usage: $0 \"your goal in natural language\"" >&2
exit 1
fi
local goal="$*"
local json thought cmd explain
json="$(propose_command "$goal")"
thought="$(jq -r '.thought // ""' <<< "$json")"
cmd="$(jq -r '.command // empty' <<< "$json")"
explain="$(jq -r '.explain // ""' <<< "$json")"
echo "Thought: $thought"
echo "Explain: $explain"
if [[ -z "$cmd" ]]; then
echo "No command proposed." >&2
exit 1
fi
if validate_and_confirm "$cmd"; then
run_with_logging "$goal" "$cmd"
else
echo "Aborted."
fi
}
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
main "$@"
fi
Make it executable:
chmod +x agent.sh
Try it:
./agent.sh "Summarize error rates from the last 100 systemd journal lines"
Example outcome:
The model might propose:
journalctl -n 100 | grep -i "error" | sort | uniq -c | sort -nr | headYou confirm, it runs, and the output plus metadata are logged to
.agent_logs/.
Step 3 — Real-world example: Log triage agent
Use the same script for guided diagnostics. Some prompts to try:
“Find the top 10 services with the most errors in the last 500 logs.”
“Show failing systemd units and the last error line for each.”
Examples:
./agent.sh "List failing systemd units and their last log line"
./agent.sh "From journalctl last 500 lines, show top 5 most frequent error messages"
These typically generate pipelines using systemctl --failed, journalctl, grep/rg, and uniq -c.
Tip: If your distribution uses systemd journals with compression, the allowlist includes zcat and zgrep.
Step 4 — Real-world example: Data wrangling assistant
Point the agent at CSVs or logs and let it suggest safe one-liners to inspect, count, and filter.
Prompts:
“Given access.log, show the top 10 IPs by request count.”
“Convert a small CSV to JSON lines (no file writes).”
“Find rows where column 3 > 100 and show the first 5.”
Runs like:
./agent.sh "From access.log, list top 10 IP addresses by frequency"
./agent.sh "Print the first 5 rows of data.csv as JSON objects using awk or jq (no writes)"
You’ll see pipelines with awk, cut, sort, uniq, jq -R -s, etc. Because we forbid writes, the agent will show previews without altering files.
Step 5 — Lightweight “memory” for traceability
The script appends a TSV line per run to .agent_logs/history.tsv:
Timestamp
Exit code
Goal
Command
This lets you search and reuse good command ideas:
grep -i "journal" .agent_logs/history.tsv | cut -f4 | sort -u
Safety, tuning, and tips
Keep the allowlist tight. Add tools intentionally as you gain confidence.
Prefer a temp or sandbox directory for exploratory runs.
Local vs cloud: For sensitive logs, use Ollama locally. For tougher reasoning, point to a cloud model by exporting
OPENAI_API_KEY.Determinism: The script uses a low temperature for more repeatable outputs. You can adjust via the provider payload.
Troubleshooting:
- Ensure Ollama is serving:
nc -z 127.0.0.1 11434should succeed. - Pull a compatible model:
ollama pull mistralorollama pull llama3. - Verify
jqandcurlare installed (see package manager commands above).
- Ensure Ollama is serving:
Bonus: Package manager installs recap
If you skipped above, here are the core dependencies again.
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl jq ripgrep
- Fedora (dnf):
sudo dnf install -y curl jq ripgrep
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y curl jq ripgrep
Ollama (vendor script):
curl -fsSL https://ollama.com/install.sh | sh
ollama serve
ollama pull mistral
Conclusion and next steps
You don’t need a heavyweight framework to get real value from AI agents. With Bash, curl, and jq, you can:
Ask an LLM to plan a safe command pipeline.
Keep control with allowlists and confirmations.
Log everything for auditability and reuse.
Next steps:
1) Save and run agent.sh against your own logs and data.
2) Expand the allowlist to include your trusted tools (kubectl get, docker ps, ps aux, etc.), staying read-only.
3) Add simple “tools” by letting the agent call named functions (e.g., tool_sysinfo) that echo text for the model to reason about.
If you build something cool—or tighten the safety model—share it. The shell has always been a glue layer; adding an LLM turns it into a powerful, conversational copilot for everyday ops.