n4nAI

Write a bash script that summarizes files with an LLM API

Learn to build a dependency-light bash script that sends file contents to an OpenAI-compatible LLM API and returns concise summaries, with error handling.

n4n Team3 min read572 words

Audio narration

Coming soon — every post will get a voice note here.

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 bash if 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 to https://api.openai.com/v1 but can be overridden with LLM_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.

Tagsbashshell-scriptingautomationllm-api

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All bash/shell scripting llm automation posts →