- Posted on
- • Artificial Intelligence
Artificial Intelligence Workflow Automation
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Bash-Powered AI: Practical Workflow Automation for Linux
If your day is spent triaging logs, writing release notes, or tagging documents, you’re doing high-value thinking with a lot of low-value glue. The hook: what if your existing Bash pipelines could call an AI model just like curl calls an API—then summarize, classify, and draft content for you?
This post shows how to bolt AI onto everyday Linux workflows using only shell tools. You’ll set up a tiny reusable Bash client, then wire it into three real-world automations you can ship today. No frameworks necessary.
Problem: Repetitive text-heavy toil slows down engineers and ops.
Value: AI turns unstructured text into structured outcomes (summaries, tags, drafts) and slots cleanly into UNIX pipelines.
Why this works: LLMs are great at text transformation; Bash is great at glue. HTTP + JSON + jq = instant AI superpowers in your shell scripts.
Prerequisites (install once)
We’ll use curl, jq, entr (a simple file-watcher), GNU parallel (optional for batch jobs), git, and make (for small build flows). Install with your system’s package manager.
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y curl jq entr parallel git make
# Optional file watcher alternative:
sudo apt install -y inotify-tools
# If you plan to use cron:
sudo apt install -y cron
sudo systemctl enable --now cron
- Fedora/RHEL (dnf):
sudo dnf install -y curl jq entr parallel git make inotify-tools
# If you plan to use cron:
sudo dnf install -y cronie
sudo systemctl enable --now crond
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y curl jq entr parallel git make inotify-tools
# If you plan to use cron:
sudo zypper install -y cronie
sudo systemctl enable --now cron
We’ll schedule with systemd timers in examples (works everywhere). Cron is optional.
Set your AI API key (OpenAI shown as an example). Store secrets securely; don’t commit them.
# Create a private env file
install -m 600 /dev/null ~/.ai.env
cat >> ~/.ai.env <<'EOF'
export OPENAI_API_KEY="REPLACE_WITH_YOUR_KEY"
export OPENAI_MODEL="gpt-4o-mini" # or another chat-capable model you have access to
# Optional: point to a compatible endpoint
# export AI_BASE_URL="https://api.openai.com/v1"
EOF
echo 'source ~/.ai.env' >> ~/.bashrc
source ~/.bashrc
Step 1: A tiny, reusable Bash AI client (ai.sh)
This script reads a prompt from stdin, calls a chat model, handles retries, and prints the model’s text response. It uses jq to build JSON safely.
#!/usr/bin/env bash
set -euo pipefail
: "${OPENAI_API_KEY:?Set OPENAI_API_KEY in ~/.ai.env}"
AI_BASE_URL="${AI_BASE_URL:-https://api.openai.com/v1}"
MODEL="${OPENAI_MODEL:-gpt-4o-mini}"
SYSTEM_PROMPT="${SYSTEM_PROMPT:-You are a concise, accurate Linux assistant.}"
TEMP="${TEMP:-0.2}"
MAX_RETRIES="${MAX_RETRIES:-5}"
prompt="$(cat)" # read all of stdin
build_payload() {
jq -n \
--arg model "$MODEL" \
--arg sys "$SYSTEM_PROMPT" \
--arg usr "$prompt" \
--argjson temp "$TEMP" \
'{
model: $model,
messages: [
{role:"system", content:$sys},
{role:"user", content:$usr}
],
temperature: $temp
}'
}
backoff=1
for attempt in $(seq 1 "$MAX_RETRIES"); do
response="$(
curl -sS --fail-with-body \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-X POST "$AI_BASE_URL/chat/completions" \
-d "$(build_payload)"
)" && break || {
status=$?
if [ "$attempt" -lt "$MAX_RETRIES" ]; then
sleep "$backoff"
backoff=$(( backoff * 2 ))
else
echo "AI request failed after $MAX_RETRIES attempts (curl exit $status)" >&2
exit 1
fi
}
done
# Print the model's answer
echo "$response" | jq -r '.choices[0].message.content'
Make it executable and test:
chmod +x ai.sh
echo "Summarize: grep, sed, and awk differences." | ./ai.sh
Step 2: Real-world automation #1 — Rolling log summaries
Turn noisy logs into concise summaries every 15 minutes. This works great for app logs, CI logs, or long task outputs.
- Script summarize-logs.sh:
#!/usr/bin/env bash
set -euo pipefail
LOG_FILE="${1:-/var/log/syslog}" # point to your app log if needed
OUT_DIR="${OUT_DIR:-$HOME/ai-summaries}"
mkdir -p "$OUT_DIR"
ts="$(date -u +"%Y-%m-%dT%H-%MZ")"
host="$(hostname)"
base="$(basename "$LOG_FILE")"
out="$OUT_DIR/${ts}-${host}-${base}.md"
# Collect a reasonable window. Adjust lines as needed.
snippet="$(tail -n 1500 "$LOG_FILE" || true)"
SYSTEM_PROMPT="You summarize Linux logs for SREs. Be precise, show top issues, error counts, unusual patterns, and actionable next steps."
export SYSTEM_PROMPT
{
echo "Summarize the following logs. Focus on anomalies, error rates, and notable events. Output Markdown with sections:"
echo
echo "```"
printf "%s\n" "$snippet"
echo "```"
} | ./ai.sh > "$out"
echo "Wrote $out"
- Run on-demand:
chmod +x summarize-logs.sh
./summarize-logs.sh /path/to/your.log
- Schedule with a user-level systemd timer (recommended):
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/log-summary.service <<'EOF'
[Unit]
Description=AI Log Summary
[Service]
Type=oneshot
Environment=OUT_DIR=%h/ai-summaries
ExecStart=%h/summarize-logs.sh %h/myapp/logs/app.log
EOF
cat > ~/.config/systemd/user/log-summary.timer <<'EOF'
[Unit]
Description=Run AI Log Summary every 15 minutes
[Timer]
OnCalendar=*:0/15
Persistent=true
[Install]
WantedBy=timers.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now log-summary.timer
systemctl --user list-timers | grep log-summary
Tip: If your logs require root, run the service as root or aggregate needed lines into a user-readable log file during your app’s logging pipeline.
Step 3: Real-world automation #2 — Generate release notes from git
Let AI group and phrase release notes from your commits since the last tag.
- Script release-notes.sh:
#!/usr/bin/env bash
set -euo pipefail
last_tag="$(git describe --tags --abbrev=0 2>/dev/null || echo "")"
range="${1:-${last_tag:+$last_tag..HEAD}}"
[ -n "$range" ] || { echo "No tags found. Pass an explicit range like: ./release-notes.sh HEAD~100..HEAD" >&2; exit 1; }
commits="$(git log --no-merges --pretty=format:'- %s (%h)' "$range")"
SYSTEM_PROMPT="You are a release manager. Group changes by features, fixes, and chores. Highlight breaking changes. Write crisp, scannable notes."
export SYSTEM_PROMPT
{
echo "Create release notes from these commits. Use Markdown with sections: Highlights, Features, Fixes, Chores. Detect breaking changes and call them out."
echo
echo "Commits:"
echo "```"
printf "%s\n" "$commits"
echo "```"
} | ./ai.sh > RELEASE_NOTES_DRAFT.md
echo "Drafted RELEASE_NOTES_DRAFT.md"
- Use it:
chmod +x release-notes.sh
./release-notes.sh # uses last_tag..HEAD
# or
./release-notes.sh HEAD~50..HEAD
Step 4: Real-world automation #3 — Auto-tag Markdown docs on save
Watch a docs directory. When a .md file changes, ask AI to propose tags, then write them to a sidecar file.
- Script tag-md.sh:
#!/usr/bin/env bash
set -euo pipefail
file="${1:?Pass a Markdown file}"
content="$(cat "$file")"
SYSTEM_PROMPT="You are a documentation assistant. Read a Markdown document and propose 3-7 concise tags (kebab-case). Return a JSON array of strings only."
export SYSTEM_PROMPT
tags="$(
{
echo "Propose tags for this document. JSON array only."
echo "```markdown"
printf "%s\n" "$content"
echo "```"
} | ./ai.sh
)"
# Try to extract a JSON array even if the model wrapped it in prose.
clean="$(printf "%s" "$tags" | jq -cr 'try (fromjson) catch input')"
sidecar="${file%.md}.tags.json"
printf "%s\n" "$clean" > "$sidecar"
echo "Wrote $sidecar"
- Watch the directory with entr:
chmod +x tag-md.sh
# Re-run the tagger when any Markdown file changes.
ls docs/*.md | entr -r ./tag-md.sh /_
- Alternative watcher (inotify-tools):
inotifywait -m -e close_write --format '%w%f' docs | while read -r f; do
[[ "$f" == *.md ]] && ./tag-md.sh "$f"
done
Scaling up safely: cost, speed, and reliability
- Cache identical prompts:
# Wrap ai.sh calls to avoid re-querying identical inputs
ai_cached() {
cache_dir="${AI_CACHE_DIR:-$HOME/.cache/ai}"
mkdir -p "$cache_dir"
key="$(cat | sha256sum | awk '{print $1}')"
cache="$cache_dir/$key.txt"
if [[ -f "$cache" ]]; then
cat "$cache"
else
# We re-build the payload by piping the original input back into ai.sh
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT
printf "%s" "$key" >/dev/null # no-op to use $key
# Re-read from stdin: capture to a buffer, then pass to ai.sh
# Usage: echo "prompt" | ai_cached
buf="$(cat)" # original stdin already consumed above for key
printf "%s" "$buf" | ./ai.sh | tee "$cache"
fi
}
Note: The above demonstrates the pattern; adapt for your exact flow. In practice, compute the hash from the full JSON payload (system prompt + model + user prompt) to avoid collisions.
- Parallelize batch transforms:
# Example: summarize all *.log in parallel (limit concurrency to 3)
ls logs/*.log | parallel -j3 './summarize-logs.sh {}'
Control spend:
- Lower temperature and keep inputs short (trim with tail -n, sed, awk).
- Prefer smaller, cheaper models for routine tasks; escalate to larger models on fallback.
- Add monthly quotas in your scripts (e.g., count requests and refuse after N).
Security:
- Keep API keys out of history and repos. Restrict ~/.ai.env to 600.
- Scrub secrets before sending logs to AI (sed patterns, e.g., redact tokens).
Why this approach is valid
Leverages what you already have: Bash, systemd, cron, git, and files.
Clear, composable contracts: AI calls are just HTTP+JSON; jq makes them robust.
Incremental adoption: start with one script (release notes or logs), then expand.
Portable: works on Debian/Ubuntu, Fedora/RHEL, and openSUSE with standard packages.
Conclusion and next steps
Your shell already orchestrates your work. Add one small AI call and those pipelines start writing, summarizing, and classifying for you.
Start now: create ai.sh, run a single example (release-notes.sh).
Then pick one noisy source (logs, docs, tickets) and automate the first 80%.
Share your best workflow with your team and standardize it behind a Makefile or systemd timer.
If you want a follow-up, ask for:
A minimal test harness to validate prompts locally
A streaming version of ai.sh for real-time progress
Provider-agnostic configuration (OpenAI-compatible endpoints, local models, etc.)
Happy automating—one small script at a time.