- Posted on
- • Artificial Intelligence
Automating Back Office Tasks with Artificial Intelligence
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Automating Back Office Tasks with Artificial Intelligence (Bash-First on Linux)
If your back office is drowning in PDFs, spreadsheets, and repetitive email replies, you’re not alone. The hidden cost of “swivel‑chair” work—copying values from invoices, sorting documents, drafting routine responses—adds up fast. The good news: AI can handle a surprising amount of this unstructured, boring work, and you can orchestrate it from the Linux command line you already know.
This guide shows how to use AI from Bash to:
Triage and route documents automatically
Extract structured data (e.g., invoice fields) into clean CSV
Draft human‑quality replies you only need to approve
Produce daily digests of activity logs
You’ll get actionable, copy‑paste‑ready scripts that run locally (Ollama) or against a cloud API (OpenAI‑compatible), with install instructions for apt, dnf, and zypper.
Why this works (and why now)
LLMs are finally good at unstructured text. They can read PDFs (after OCR), understand context, and emit clean JSON for your pipelines.
Linux shines at glue work. Tools like
inotifywait,jq, andcronmake it easy to watch folders, parse responses, and schedule routines.Privacy and control are possible. Run models locally with Ollama for sensitive data, or swap to a cloud API when you need more accuracy or scale.
Real ROI. Automating even 30–60 minutes/day of manual triage or data entry pays back quickly.
Prerequisites and installation
You’ll need a few standard CLI tools. Choose the package manager for your distro:
- Ubuntu/Debian (apt):
sudo apt update
sudo apt install -y curl jq inotify-tools poppler-utils ocrmypdf tesseract-ocr tesseract-ocr-eng cron bsd-mailx
- Fedora/RHEL (dnf):
sudo dnf install -y curl jq inotify-tools poppler-utils ocrmypdf tesseract tesseract-langpack-eng cronie mailx
- openSUSE (zypper):
sudo zypper install -y curl jq inotify-tools poppler-tools ocrmypdf tesseract-ocr tesseract-ocr-traineddata-english cron mailx
Enable cron (so scheduled jobs can run):
- Ubuntu/Debian:
sudo systemctl enable --now cron
- Fedora/RHEL:
sudo systemctl enable --now crond
- openSUSE:
sudo systemctl enable --now cron
Optional: local AI with Ollama (recommended for privacy):
curl -fsSL https://ollama.com/install.sh | sh
ollama pull llama3
Environment options (pick one backend):
- Local (Ollama):
export LLM_BACKEND=ollama
export OLLAMA_MODEL=llama3
- Cloud (OpenAI‑compatible Chat Completions):
export LLM_BACKEND=openai
export OPENAI_API_KEY=sk-yourkey
export OPENAI_MODEL=gpt-4o-mini
export OPENAI_BASE_URL=https://api.openai.com
Security tip: Store secrets in a dedicated .env with chmod 600, and source it in your scripts.
Reusable Bash helper: ask an LLM
Drop this into ~/backoffice/bin/llm.sh and chmod +x it. It abstracts a local Ollama or OpenAI‑compatible call and safely escapes prompt text.
#!/usr/bin/env bash
set -euo pipefail
ask_llm() {
# Usage: ask_llm "prompt text" "system text" ["json"|"plain"]
local prompt="${1:-}"
local system="${2:-You are a precise assistant. Keep answers concise.}"
local mode="${3:-plain}"
if [[ "${LLM_BACKEND:-ollama}" == "ollama" ]]; then
local fmt=""
[[ "$mode" == "json" ]] && fmt='"format":"json",'
curl -sS http://localhost:11434/api/chat \
-H 'Content-Type: application/json' \
-d "{
\"model\":\"${OLLAMA_MODEL:-llama3}\",
\"messages\":[
{\"role\":\"system\",\"content\":$(
jq -Rs . <<< "$system"
)},
{\"role\":\"user\",\"content\":$(
jq -Rs . <<< "$prompt"
)}
],
$fmt
\"stream\": false
}" | jq -r '.message.content'
else
local resp_fmt=''
[[ "$mode" == "json" ]] && resp_fmt=',"response_format":{"type":"json_object"}'
curl -sS "${OPENAI_BASE_URL:-https://api.openai.com}/v1/chat/completions" \
-H "Authorization: Bearer ${OPENAI_API_KEY:?Set OPENAI_API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"model\":\"${OPENAI_MODEL:-gpt-4o-mini}\",
\"temperature\": 0.2
$resp_fmt,
\"messages\":[
{\"role\":\"system\",\"content\":$(
jq -Rs . <<< "$system"
)},
{\"role\":\"user\",\"content\":$(
jq -Rs . <<< "$prompt"
)}
]
}" | jq -r '.choices[0].message.content'
fi
}
Source it in other scripts:
source "$HOME/backoffice/bin/llm.sh"
Actionable automation 1: Auto‑triage PDFs into folders
Watch a directory for new PDFs, OCR/parse them, and let the LLM classify and rename files. This removes a ton of manual sorting.
1) Prepare folders:
mkdir -p ~/backoffice/{inbox,processed,errors,classified/{invoice,receipt,contract,statement,po,other}}
2) Save as ~/backoffice/bin/triage.sh:
#!/usr/bin/env bash
set -euo pipefail
source "$HOME/backoffice/bin/llm.sh"
INBOX="$HOME/backoffice/inbox"
PROCESSED="$HOME/backoffice/processed"
ERRORS="$HOME/backoffice/errors"
DEST="$HOME/backoffice/classified"
extract_text() {
local pdf="$1" outtxt="$2"
local tmp_pdf
tmp_pdf="$(mktemp --suffix=.pdf)"
# Sidecar text; skip OCR if text exists
if ocrmypdf --sidecar "$outtxt" --skip-text "$pdf" "$tmp_pdf" >/dev/null 2>&1; then
:
else
# Fallback: plain text extraction
pdftotext -layout "$pdf" "$outtxt" || true
fi
rm -f "$tmp_pdf"
}
classify_and_move() {
local pdf="$1"
local base; base="$(basename "$pdf" .pdf)"
local txt; txt="$(mktemp)"
trap 'rm -f "$txt"' EXIT
extract_text "$pdf" "$txt"
local content; content="$(cat "$txt")"
local sys="You classify business documents. Allowed categories: invoice, receipt, contract, statement, purchase_order, other.
Return strict JSON with:
category: one of [invoice, receipt, contract, statement, po, other] (use 'po' for purchase_order),
tags: array of 1-5 lowercase short tags,
suggested_filename: lowercase slug with optional ISO date (YYYY-MM-DD) and a short title. Don't include file extension."
local prompt="Document text:
---
$content
---
Return only JSON."
local resp
if ! resp="$(ask_llm "$prompt" "$sys" json)"; then
echo "LLM error; moving to $ERRORS" >&2
mv -f "$pdf" "$ERRORS/$(basename "$pdf")"
return
fi
# Validate JSON
if ! echo "$resp" | jq . >/dev/null 2>&1; then
echo "Invalid JSON; moving to $ERRORS" >&2
mv -f "$pdf" "$ERRORS/$(basename "$pdf")"
return
fi
local cat fname
cat="$(echo "$resp" | jq -r '.category // "other"')"
# normalize purchase_order -> po
[[ "$cat" == "purchase_order" ]] && cat="po"
fname="$(echo "$resp" | jq -r '.suggested_filename // "'"$base"'"')"
[[ -z "$fname" || "$fname" == "null" ]] && fname="$base"
local target_dir="$DEST/$cat"
mkdir -p "$target_dir"
mv -f "$pdf" "$target_dir/$fname.pdf"
echo "Filed $(basename "$pdf") -> $target_dir/$fname.pdf"
}
mkdir -p "$INBOX" "$PROCESSED" "$ERRORS" "$DEST"
# One-shot over existing files (idempotent)
shopt -s nullglob
for f in "$INBOX"/*.pdf "$INBOX"/*.PDF; do
classify_and_move "$f"
done
# Watch for new PDFs
command -v inotifywait >/dev/null 2>&1 || {
echo "inotifywait not found; install inotify-tools" >&2; exit 1; }
inotifywait -m -e close_write --format '%f' "$INBOX" | while read -r file; do
case "$file" in
*.pdf|*.PDF) classify_and_move "$INBOX/$file" ;;
*) : ;;
esac
done
3) Run it:
bash ~/backoffice/bin/triage.sh
Result: New PDFs dropped into ~/backoffice/inbox get OCR’d, classified, renamed, and filed into ~/backoffice/classified/....
Actionable automation 2: Extract invoice data into CSV
Turn messy invoices into clean records your finance tools can ingest.
1) Save as ~/backoffice/bin/invoice_extract.sh:
#!/usr/bin/env bash
set -euo pipefail
source "$HOME/backoffice/bin/llm.sh"
SRC_DIR="$HOME/backoffice/classified/invoice"
DATA_DIR="$HOME/backoffice/data"
OUT_CSV="$DATA_DIR/invoices.csv"
mkdir -p "$DATA_DIR"
[[ -f "$OUT_CSV" ]] || echo "vendor,invoice_no,issue_date,due_date,currency,total,filename" > "$OUT_CSV"
extract_text_quick() {
local pdf="$1" outtxt="$2"
# Fast path: try pdftotext first
if ! pdftotext -layout "$pdf" "$outtxt" >/dev/null 2>&1; then
# Fallback to OCR sidecar
local tmp_pdf
tmp_pdf="$(mktemp --suffix=.pdf)"
ocrmypdf --sidecar "$outtxt" --skip-text "$pdf" "$tmp_pdf" >/dev/null 2>&1 || true
rm -f "$tmp_pdf"
fi
}
for pdf in "$SRC_DIR"/*.pdf; do
[[ -e "$pdf" ]] || continue
tmp="$(mktemp)"; trap 'rm -f "$tmp"' EXIT
extract_text_quick "$pdf" "$tmp"
content="$(cat "$tmp")"
sys="You extract invoice fields and return strict JSON with keys:
vendor (string), invoice_no (string), issue_date (YYYY-MM-DD), due_date (YYYY-MM-DD or null),
currency (ISO code like USD/EUR), total (number).
Infer missing fields conservatively; use null when uncertain. Return only JSON."
prompt="Invoice text:
---
$content
---"
if ! json="$(ask_llm "$prompt" "$sys" json)"; then
echo "LLM error on $pdf" >&2
continue
fi
# Coerce to CSV row
row="$(echo "$json" | jq -r --arg f "$(basename "$pdf")" \
'[.vendor,.invoice_no,.issue_date, ( .due_date // "" ), .currency, ( .total // "" ), $f] | @csv')"
echo "$row" >> "$OUT_CSV"
echo "Appended $(basename "$pdf") -> $OUT_CSV"
done
2) Run it after triage:
bash ~/backoffice/bin/invoice_extract.sh
You’ll get a growing ~/backoffice/data/invoices.csv you can feed into accounting or BI tools.
Actionable automation 3: Draft email replies from templates
Generate drafts you review and send—perfect for routine customer queries or status updates.
1) Prepare folders:
mkdir -p ~/backoffice/drafts/inbox ~/backoffice/drafts/outbox
2) Drop a plain text customer message into ~/backoffice/drafts/inbox/request-123.txt.
3) Create ~/backoffice/bin/reply_drafter.sh:
#!/usr/bin/env bash
set -euo pipefail
source "$HOME/backoffice/bin/llm.sh"
INBOX="$HOME/backoffice/drafts/inbox"
OUTBOX="$HOME/backoffice/drafts/outbox"
sys="You are a professional customer support agent.
Write friendly, concise, and accurate replies. Use British English.
Include a clear subject line and numbered next steps when appropriate.
If information is missing, ask 1-2 specific questions."
for f in "$INBOX"/*.txt; do
[[ -e "$f" ]] || continue
prompt="$(cat "$f")"
draft="$(ask_llm "$prompt" "$sys" plain)"
out="$OUTBOX/$(basename "${f%.txt}").md"
{
echo "$draft"
echo
echo "<!-- Review before sending. Save subject on first line prefixed by 'Subject: ' -->"
} > "$out"
echo "Drafted: $out"
done
4) Review the Markdown drafts in outbox/, edit as needed, then optionally send with mailx (replace addresses):
to="customer@example.com"
file="~/backoffice/drafts/outbox/request-123.md"
subject="$(grep -m1 -E '^Subject:' "$file" | sed 's/^Subject:[[:space:]]*//')"
body="$(sed '/^Subject:/d' "$file")"
echo "$body" | mailx -s "$subject" "$to"
Note: On Debian/Ubuntu, mailx comes from bsd-mailx. You may need to configure an MTA (e.g., msmtp, postfix) or use your org’s relay.
Actionable automation 4: Daily digest via cron
Summarize logs or ticket updates into a clean daily report.
1) Create directories and a sample log:
mkdir -p ~/backoffice/updates ~/backoffice/reports
echo "[10:04] Ticket #842 escalated; awaiting vendor ETA
[12:17] 18 invoices posted
[15:32] Payroll export validated" > ~/backoffice/updates/$(date +%F).log
2) Save as ~/backoffice/bin/digest.sh:
#!/usr/bin/env bash
set -euo pipefail
source "$HOME/backoffice/bin/llm.sh"
UPDATES="$HOME/backoffice/updates"
OUTDIR="$HOME/backoffice/reports"
today="$(date +%F)"
log="$UPDATES/$today.log"
out="$OUTDIR/daily-$today.md"
[[ -f "$log" ]] || { echo "No log for $today"; exit 0; }
sys="You are an operations analyst. Summarize the day's updates into a brief Markdown report with:
- bullets grouped by theme,
- notable risks/blockers,
- action items with owners if present."
content="$(cat "$log")"
report="$(ask_llm "$content" "$sys" plain)"
echo -e "# Daily Digest ($today)\n\n$report" > "$out"
echo "Wrote $out"
3) Schedule on weekdays at 17:00:
crontab -e
Add:
0 17 * * 1-5 LLM_BACKEND=ollama OLLAMA_MODEL=llama3 bash $HOME/backoffice/bin/digest.sh >> $HOME/backoffice/logs/cron.log 2>&1
For OpenAI‑compatible:
0 17 * * 1-5 LLM_BACKEND=openai OPENAI_API_KEY=... OPENAI_MODEL=gpt-4o-mini OPENAI_BASE_URL=https://api.openai.com bash $HOME/backoffice/bin/digest.sh >> $HOME/backoffice/logs/cron.log 2>&1
Create the logs dir if desired:
mkdir -p ~/backoffice/logs
Real‑world tips
Start local, then scale: Try Ollama for private data. If accuracy is insufficient, switch to a stronger cloud model by flipping env vars.
Keep a paper trail: Save LLM JSON outputs alongside source docs. It helps with audits and troubleshooting.
Validate aggressively: Always check JSON with
jq. On parse errors, route toerrors/for manual review.Guard secrets: Never hardcode API keys. Use
.env, environment variables, and least‑privilege.Iterate prompts: Small wording changes can improve consistency (e.g., “Return strict JSON with these keys only”).
Conclusion and next step (CTA)
You now have a practical Bash toolkit to:
File and rename PDFs automatically
Extract invoice data into clean CSV
Draft accurate customer replies
Produce daily operational digests
Pick one workflow and wire it up today—start with the triage script, then add extraction and summaries. Once you see the time saved, expand to new document types and teams.
If you’d like a follow‑up post with systemd units, containerization, or a full logging/metrics setup, let me know which workflow you’re automating and what model/backend you’re using.