- Posted on
- • Artificial Intelligence
Learning Prompt Engineering
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
Learning Prompt Engineering (for Bash Users)
Ever stared at a blinking cursor thinking “there must be a one-liner for this,” only to spend 30 minutes copy-pasting from random blogs? Prompt engineering lets you turn fuzzy intents into reliable, scriptable outputs from an LLM—so you can move faster, avoid footguns, and integrate AI cleanly into your Linux workflow.
This post shows you how to approach prompt engineering like a shell user: deterministic, testable, and automatable. You’ll get concrete Bash patterns, JSON validation with jq, and a tiny harness you can drop into your dotfiles.
Why this is worth your time
Repeatability: Prompts that specify role, format, and constraints give you predictable outputs you can parse and use in scripts.
Safety: Good prompts reduce risky suggestions (like
rm -rf /) by stating constraints up front.Composability: Structured outputs (e.g., JSON) make it easy to chain results with standard CLI tools.
Speed: A solid template plus a small Bash wrapper turns “ask the model” into a command you can call anywhere.
Prerequisites (Linux)
We’ll use only standard CLI tools to talk to an LLM API and validate outputs.
Install curl and jq:
- Debian/Ubuntu (apt):
sudo apt update && sudo apt install -y curl jq
- Fedora/RHEL/CentOS (dnf):
sudo dnf install -y curl jq
- openSUSE (zypper):
sudo zypper install -y curl jq
Optional (nicer HTTP UX):
- Debian/Ubuntu (apt):
sudo apt update && sudo apt install -y httpie
- Fedora/RHEL/CentOS (dnf):
sudo dnf install -y httpie
- openSUSE (zypper):
sudo zypper install -y httpie
Set your API key (example shows OpenAI; adapt URL/headers for your provider):
export OPENAI_API_KEY="sk-...your-key..."
1) Start with a strong prompt skeleton
Good prompts are like good shell scripts: they declare purpose, inputs, constraints, and outputs. Use a reusable template with clear sections.
read -r -d '' PROMPT <<'EOF'
[Role]
You are a senior Linux SRE. You prioritize safety, clarity, and portability.
[Task]
Given the user's goal and environment, propose a command or short script to solve it.
[Constraints]
- Prefer POSIX sh-compatible syntax unless asked otherwise.
- Explain trade-offs briefly.
- Never run destructive operations without safety flags or a dry-run option.
- If the task is ambiguous, ask 1–2 clarifying questions.
[Output]
Return a JSON object with:
- "command": the exact command (one line if possible)
- "explanation": 2–3 concise bullet points
- "notes": any assumptions or caveats
[User Goal]
Back up /etc to a timestamped archive under ~/backups, excluding VCS dirs, compression welcome.
EOF
Key ideas:
Role: sets expertise and tone.
Task: target outcome.
Constraints: safety, style, portability.
Output: explicit schema for parsing.
2) Feed the model real context from your shell
Models do better with relevant, concrete text. Pipe in --help output, snippets of config, or directory listings. Always fence it and label it.
HELP_TEXT="$(rsync --help 2>&1 | head -n 80)" # keep context brief and relevant
read -r -d '' CONTEXT <<EOF
[Reference: rsync --help (truncated)]
<<<
$HELP_TEXT
>>>
EOF
USER_PROMPT="$PROMPT
[Additional Context]
$CONTEXT"
Tips:
Keep context minimal and targeted.
Use clear delimiters like <<< >>> so the model knows where context starts/ends.
Never paste secrets.
3) Demand structure and validate with jq
Ask for JSON and then actually check it. Build the request body safely with jq to avoid quoting bugs.
API_URL="https://api.openai.com/v1/chat/completions"
MODEL="gpt-4o-mini" # use a model that supports JSON output; adjust as needed
TEMP="0.2" # lower = more deterministic
jq -n \
--arg model "$MODEL" \
--arg sys "You are a precise, safety-first Linux SRE. Be concise." \
--arg user "$USER_PROMPT" \
--argjson temp "$TEMP" \
'
{
model: $model,
temperature: $temp,
response_format: { type: "json_object" },
messages: [
{role: "system", content: $sys},
{role: "user", content: $user}
]
}' \
| curl -sS \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d @- "$API_URL" \
| tee response.raw.json \
| jq -r '.choices[0].message.content' \
| tee response.content.json \
| jq .
Notes:
response_format: {type: "json_object"}requests well-formed JSON. If your model/provider doesn’t support it, keep the schema in the prompt and still parse/validate with jq.Always pipe through
jq—you’ll catch errors early.
4) Wrap it in a tiny Bash harness and iterate
Turn the above into a function you can reuse. Keep it simple and parameterized.
llm() {
local prompt_file="$1" # path to a prompt file (text)
local model="${2:-gpt-4o-mini}"
local temp="${3:-0.2}"
local api_url="${API_URL:-https://api.openai.com/v1/chat/completions}"
[[ -z "$OPENAI_API_KEY" ]] && { echo "OPENAI_API_KEY not set" >&2; return 1; }
[[ ! -f "$prompt_file" ]] && { echo "Prompt file not found: $prompt_file" >&2; return 1; }
local user_text
user_text="$(cat "$prompt_file")"
jq -n \
--arg model "$model" \
--arg sys "You are a precise, safety-first Linux SRE. Be concise." \
--arg user "$user_text" \
--argjson temp "$temp" \
'{
model: $model,
temperature: $temp,
response_format: { type: "json_object" },
messages: [
{role:"system", content:$sys},
{role:"user", content:$user}
]
}' \
| curl -sS \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d @- "$api_url" \
| jq -r '.choices[0].message.content'
}
Use it like:
llm prompt.txt | jq .
Add a quick schema check (informal) to ensure required keys exist before acting:
OUT="$(llm prompt.txt)"
echo "$OUT" | jq -e 'has("command") and has("explanation")' >/dev/null \
|| { echo "Missing keys in response"; exit 1; }
CMD="$(echo "$OUT" | jq -r '.command')"
echo "Proposed command:"
echo "$CMD"
# Safety: show a dry-run first if supported
case "$CMD" in
*rsync* ) echo "Dry run:"; echo "$CMD" --dry-run ;;
*tar* ) echo "Listing mode:"; echo "$CMD" | sed 's/ -f / -tvf /' ;;
* ) echo "Review carefully before executing." ;;
esac
Real-world example: safer backups with structure
Prompt file (backup_prompt.txt):
[Role]
You are a senior Linux SRE. Safety and portability first.
[Task]
Propose a command to back up /etc into ~/backups as a timestamped archive, excluding VCS directories (.git, .svn). Prefer a widely available compressor. Include a safe preview option.
[Constraints]
- POSIX sh-compatible if possible.
- Avoid destructive operations.
- Keep the command to one line if feasible.
- If multiple options exist, choose the simplest that ships broadly on Linux servers.
[Output]
Return JSON with keys:
- "command": the single-line command to create the backup
- "explanation": 2–3 bullet points
- "preview": a safe command to verify what would be included without writing the full archive
- "notes": any assumptions
[System details]
Home dir: ~
Target dir: ~/backups
Run:
OUT="$(llm backup_prompt.txt)"
echo "$OUT" | jq .
Expected shape (you’ll get actual content from the model):
{
"command": "mkdir -p \"$HOME/backups\" && tar -C / --exclude='.git' --exclude='.svn' -czf \"$HOME/backups/etc-$(date +%F_%H%M%S).tar.gz\" etc",
"explanation": [
"Uses tar with gzip for broad availability.",
"Excludes typical VCS directories."
],
"preview": "tar -C / --exclude='.git' --exclude='.svn' -tzf \"$HOME/backups/etc-TEST.tar.gz\" etc || tar -C / --exclude='.git' --exclude='.svn' -cf - etc > /dev/null",
"notes": "On minimal systems without gzip, replace -czf with -cf and compress later."
}
Then you can inspect and decide to run:
CMD="$(echo "$OUT" | jq -r '.command')"
echo "To execute after review:"
echo "$CMD"
Extra tips
Be explicit about non-goals: “Do not modify files,” “Do not assume root.”
Control variability: set temperature to 0–0.3 for reproducibility.
Keep prompts and context in version control. Treat them like code.
Log and diff responses when you iterate, just like you would with config changes.
Conclusion and next step (CTA)
Prompt engineering isn’t magic words—it’s disciplined specification. Start by:
1) Installing curl and jq,
2) Creating a prompt skeleton with role, task, constraints, and output schema,
3) Building a tiny llm function that requests JSON and validates it with jq.
Pick one recurring task (backups, log parsing, safe cleanup), write a prompt file, and run it through your harness. Iterate until the output is predictable enough to trust—then integrate it into your scripts.
If you found this useful, turn your best prompt into a reusable template in your dotfiles and share a gist with the community.