A practical bash script summarize files llm api workflow lets you pipe local text into a model without leaving the terminal. This tutorial builds a small, dependency-light tool that reads files, calls an OpenAI-compatible chat endpoint, and prints concise summaries. By the end you’ll have a reusable script that handles multiple files, skips binaries, and degrades gracefully on API errors.
Prerequisites
Before writing any code, confirm you have the following:
- bash 4.0+ (macOS ships 3.2; use
brew install bashif needed) - curl for HTTP requests
- jq for safe JSON construction and parsing
- An API key for an OpenAI-compatible LLM API. Set it in your environment as
LLM_API_KEY. The base URL defaults tohttps://api.openai.com/v1but can be overridden withLLM_BASE_URL. If you route through a gateway such as n4n.ai, you get one OpenAI-compatible endpoint that addresses 240+ models with automatic fallback when a provider is degraded—but the script below works against any compliant server.
Verify jq is installed:
jq --version || echo "install jq first"
Step 1: Probe the endpoint with a raw call
Never trust your shell quoting until you’ve seen a successful response. Export credentials and fire a minimal request.
export LLM_API_KEY="sk-your-key"
export LLM_BASE_URL="https://api.openai.com/v1"
MODEL="gpt-4o-mini"
curl -s "$LLM_BASE_URL/chat/completions" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"'"$MODEL"'","messages":[{"role":"user","content":"Reply with the word pong."}]}'
Expected output is a JSON object. The relevant field is choices[0].message.content. Pipe through jq -r '.choices[0].message.content' and you should see pong. If you get 401, your key is wrong. If you get 429, you’re rate-limited.
Step 2: Construct the bash script summarize files llm api core
Create summarize.sh. We use jq -n to build the payload because hand-escaping newlines and quotes in bash is a bug farm. The function below takes a filepath, reads it, and posts a summarization prompt.
#!/usr/bin/env bash
set -euo pipefail
LLM_BASE_URL="${LLM_BASE_URL:-https://api.openai.com/v1}"
LLM_API_KEY="${LLM_API_KEY:?Set LLM_API_KEY in environment}"
MODEL="${MODEL:-gpt-4o-mini}"
summarize_file() {
local file="$1"
local content
content=$(cat "$file")
local prompt
prompt="Summarize the following file in 3 concise bullet points. If it is code, focus on purpose and structure:\n\n${content}"
local payload
payload=$(jq -n \
--arg model "$MODEL" \
--arg prompt "$prompt" \
'{
model: $model,
messages: [{role: "user", content: $prompt}],
temperature: 0.3,
max_tokens: 300
}')
local response
response=$(curl -s "$LLM_BASE_URL/chat/completions" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d "$payload")
# Extract content, fail loudly if structure missing
echo "$response" | jq -r '.choices[0].message.content // empty'
}
The // empty guards against null. If the API returns an error JSON without choices, jq prints nothing and the script continues; we’ll tighten that later.
Step 3: Guard against large and binary files
LLM context windows are finite and you pay per token. Blindly cat-ing a 2 GB log will blow up. Use file to detect text and head -c to cap bytes.
summarize_file() {
local file="$1"
if [[ ! -f "$file" ]]; then
echo "ERROR: $file not found" >&2
return 1
fi
if ! file "$file" | grep -q "text"; then
echo "SKIP: $file is binary" >&2
return 0
fi
local content
content=$(head -c 8000 "$file")
local prompt
prompt="Summarize the following file excerpt in 3 bullets:\n\n${content}"
# ... rest identical to step 2
}
Eight thousand bytes is roughly 2k tokens—safe for most small models. For source trees, this catches the header and early functions.
Step 4: Iterate over CLI arguments
The script should accept multiple paths. Add a main function and call it at the bottom.
main() {
if [[ $# -eq 0 ]]; then
echo "Usage: $0 <file> [<file> ...]" >&2
exit 1
fi
for f in "$@"; do
echo "### Summary for $f"
if summarize_file "$f"; then
echo
fi
done
}
main "$@"
Now the bash script summarize files llm api handles batches. Run it:
./summarize.sh README.md config.json
Expected output:
### Summary for README.md
- Project is a CLI tool for log rotation.
- Uses signal handling for graceful shutdown.
- Build instructions use Make.
### Summary for config.json
- Defines server port and timeout.
- Enables debug mode.
- Lists allowed origins.
Step 5: Harden error handling and retries
Production shell scripts need to detect HTTP failures. curl -f makes it exit non-zero on >=400. Add --retry 3 --retry-delay 2 for transient network blips. Also capture jq errors.
response=$(curl -sf --retry 3 "$LLM_BASE_URL/chat/completions" \
-H "Authorization: Bearer $LLM_API_KEY" \
-H "Content-Type: application/json" \
-d "$payload") || {
echo "API call failed for $file" >&2
return 1
}
if ! echo "$response" | jq -e '.choices[0].message.content' >/dev/null; then
echo "Malformed response: $response" >&2
return 1
fi
We used jq -e to check existence. This prevents empty summaries from silently passing.
Step 6: Optional caching via provider hints
If you query the same file repeatedly, you can cut cost by sending cache control directives. Some gateways forward provider cache-control hints; for example, n4n.ai honors client routing directives and will pass cache_control to the upstream provider when supported. In bash, add a system message with the hint:
payload=$(jq -n \
--arg model "$MODEL" \
--arg prompt "$prompt" \
'{
model: $model,
messages: [
{role: "system", content: "You are a summarizer."},
{role: "user", content: $prompt, "cache_control": {"type": "ephemeral"}}
],
temperature: 0.3
}')
Not all models support this; test before relying on it.
Step 7: Make it a pipeline-friendly tool
Sometimes you want to summarize stdin. Add a - sentinel:
if [[ "$file" == "-" ]]; then
content=$(head -c 8000)
else
content=$(head -c 8000 "$file")
fi
Now cat error.log | ./summarize.sh - works.
Final script and takeaways
The complete summarize.sh is about 60 lines. It demonstrates that a bash script summarize files llm api does not require Python or Node—just curl and jq. Keep secrets out of the payload, cap input size, and fail explicitly. For larger corpora, wrap it in find . -name '*.py' -print0 | xargs -0 ./summarize.sh and redirect to a report.
If you need to swap models or avoid vendor lock-in, point LLM_BASE_URL at any OpenAI-compatible gateway. The script stays identical; only the environment changes.