Write idiomatic, efficient Bash and Shell scripts
A before/after bash scripting reference for quoting, conditionals, loops, pipes, functions, and error handling.
Maintainer of this project? Claim this page to edit the listing.
13.6.1Add to Favorites
Why it matters
Developers hire this skill to generate, review, and optimize Bash and Shell scripts that follow best practices, use idiomatic patterns, and maximize efficiency while avoiding common pitfalls and anti-patterns.
Outcomes
What it gets done
Generate Bash scripts using idiomatic patterns and modern syntax
Review existing shell scripts for efficiency and correctness issues
Refactor legacy scripts to use safer, more maintainable constructs
Debug shell scripts and identify performance bottlenecks
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-bash | bash Overview
Bash / Shell: Idiomatic Efficiency Reference
A before/after bash scripting reference covering quoting, conditionals, loops, pipes, functions, and error handling patterns. Use when writing or reviewing bash or shell scripts and want idiomatic, safer patterns applied consistently.
What it does
Bash / Shell: Idiomatic Efficiency Reference is a skill of before/after bash coding guidelines across seven areas: quoting and word splitting, conditionals and tests, loops and iteration, pipes and process substitution, functions and return values, error handling, and a summary anti-pattern table.
When to use - and when NOT to
Use this when writing or reviewing bash or shell scripts and want idiomatic, safer patterns applied consistently rather than ad hoc style. It's language-specific guidance, not an architectural framework, and the skill itself notes that over-compressing a script can hurt readability, so judgement still applies.
Inputs and outputs
Quoting rules: double-quote every $variable and $(command) unless splitting is intentional, use "${files[@]}" in loops, prefer $() over backticks for nesting, and quote both sides of string comparisons. Conditionals: prefer [[ ]] over [ ] for safer && and || with no internal word splitting, test a command's exit status directly (if grep -q pattern file) instead of checking $? afterward, and use (( )) for arithmetic comparisons instead of [ "$count" -gt 10 ]. Loops: glob directly (for f in *.txt) instead of parsing ls output, read files line-by-line with while IFS= read -r line rather than for line in $(cat file), use brace expansion {1..10} or C-style (( )) instead of seq, and redirect input into a while loop rather than piping into it, since piping runs the loop in a subshell that silently discards variable updates made inside it. Pipes: avoid chained grep | grep in favor of a single regex or awk, avoid cat file | grep as a useless use of cat, use process substitution <(cmd1) instead of temp files for diffing two commands' output, and set -o pipefail so a failing early command in a pipeline isn't hidden by a later command's success. Functions: use echo plus command substitution to return string values, since return is only for 0-255 exit codes, keep function-local state with local instead of mutating globals, and define functions as name() {} rather than the non-POSIX function keyword.
Integrations
Error handling: start every script with set -euo pipefail, removed selectively only where genuinely needed, check command results explicitly (cd /some/dir || exit 1) rather than letting a failed cd fall through into a destructive rm -rf, use trap 'rm -f "$tmpfile"' EXIT for guaranteed cleanup, and never silence errors with 2>/dev/null without an explicit || true showing the failure was anticipated. A closing anti-pattern table maps each bad pattern - ls parsing, cat | grep, unquoted vars, eval with user input, #!/bin/sh with bash features, expr for math - to its preferred replacement.
Who it's for
Developers writing or reviewing bash and shell scripts who want a concrete before/after reference for idiomatic, safer patterns instead of relying on habit or memory.
Source README
Bash / Shell: Idiomatic Efficiency Reference
When to Use
- Use this skill when the task matches this description: Language-specific super-code guidelines for bash.
Table of Contents
- Quoting & Word Splitting
- Conditionals & Tests
- Loops & Iteration
- Pipes & Process Substitution
- Functions & Return Values
- Error Handling
- Anti-patterns specific to Bash
1. Quoting & Word Splitting {#quoting}
### ❌ Unquoted variable (word splitting + globbing)
for f in $files; do rm $f; done
### ✅
for f in "${files[@]}"; do rm -- "$f"; done
### ❌ Unquoted command substitution
path=$(find . -name config)
cat $path # breaks on spaces
### ✅
path="$(find . -name config)"
cat "$path"
### ❌ Using backticks for command substitution
result=`echo hello`
### ✅ — $() nests cleanly
result=$(echo hello)
### ❌ String comparison without quotes
if [ $var = "hello" ]; then # breaks if var is empty or has spaces
### ✅
if [[ "$var" = "hello" ]]; then
Rule: double-quote every $variable and $(command) unless you specifically need splitting.
2. Conditionals & Tests {#conditionals}
### ❌ Single bracket test (POSIX but fragile)
if [ -f "$file" -a -r "$file" ]; then
### ✅ — [[ is safer, supports &&/||, no word splitting inside
if [[ -f "$file" && -r "$file" ]]; then
### ❌ Testing command exit status with if [ $? -eq 0 ]
grep -q pattern file
if [ $? -eq 0 ]; then echo "found"; fi
### ✅ — test command directly
if grep -q pattern file; then echo "found"; fi
### ❌ String equality with == outside [[
if [ "$a" == "$b" ]; then # == not POSIX in [ ]
### ✅
if [[ "$a" == "$b" ]]; then # Bash
### or POSIX:
if [ "$a" = "$b" ]; then
### ❌ Arithmetic with [ ] and string comparison
if [ "$count" -gt 10 ]; then
### ✅ — (( )) for arithmetic
if (( count > 10 )); then
3. Loops & Iteration {#loops}
### ❌ Parsing ls output
for f in $(ls *.txt); do process "$f"; done
### ✅ — glob directly
for f in *.txt; do
[[ -e "$f" ]] || continue # handle no-match
process "$f"
done
### ❌ Reading file line-by-line with for
for line in $(cat file.txt); do # splits on words, not lines
### ✅
while IFS= read -r line; do
process "$line"
done < file.txt
### ❌ Seq for counting
for i in $(seq 1 10); do
### ✅ — brace expansion (Bash)
for i in {1..10}; do
### or C-style:
for (( i = 1; i <= 10; i++ )); do
### ❌ Processing command output line-by-line with pipe (subshell trap)
count=0
cat file.txt | while read -r line; do
(( count++ )) # count resets after loop — subshell
done
echo "$count" # always 0
### ✅ — redirect, not pipe
count=0
while IFS= read -r line; do
(( count++ ))
done < file.txt
echo "$count"
4. Pipes & Process Substitution {#pipes}
### ❌ Chained grep | grep for AND
grep "error" log.txt | grep "timeout"
### ✅ — single grep with pattern
grep -E "error.*timeout|timeout.*error" log.txt
### or awk for complex logic:
awk '/error/ && /timeout/' log.txt
### ❌ cat + pipe (UUOC — Useless Use of Cat)
cat file.txt | grep pattern
### ✅
grep pattern file.txt
### ❌ Temp file for diff between commands
cmd1 > /tmp/a.txt
cmd2 > /tmp/b.txt
diff /tmp/a.txt /tmp/b.txt
rm /tmp/a.txt /tmp/b.txt
### ✅ — process substitution
diff <(cmd1) <(cmd2)
### ❌ Ignoring pipe failures (only last command's exit code)
false | true
echo $? # 0 — false's failure hidden
### ✅
set -o pipefail
false | true
echo $? # 1
5. Functions & Return Values {#functions}
### ❌ Using return for string values
get_name() {
return "Alice" # return is for exit codes (0-255)
}
### ✅ — echo + capture
get_name() {
echo "Alice"
}
name=$(get_name)
### ❌ Global variables modified inside functions
result=""
compute() { result="done"; }
### ✅ — use local, return via stdout
compute() {
local tmp
tmp=$(do_work)
echo "$tmp"
}
result=$(compute)
### ❌ Function keyword (not POSIX)
function my_func {
### ✅
my_func() {
6. Error Handling {#errors}
### ❌ No error handling — script continues after failure
cd /some/dir
rm -rf * # if cd fails, deletes from wrong directory
### ✅
set -euo pipefail
cd /some/dir || { echo "cd failed" >&2; exit 1; }
rm -rf ./*
### ❌ No cleanup on exit
tmpfile=$(mktemp)
### ... script might exit early, leaving tmpfile
### ✅ — trap for cleanup
tmpfile=$(mktemp)
trap 'rm -f "$tmpfile"' EXIT
### ❌ Silencing errors blindly
command 2>/dev/null
### ✅ — redirect only when you know what you're suppressing
command 2>/dev/null || true # explicit: we expect and accept failure
Start every script with set -euo pipefail. Remove selectively where needed.
7. Anti-patterns specific to Bash {#antipatterns}
| Anti-pattern | Preferred |
|---|---|
Parsing ls output |
glob: for f in *.txt |
cat file | grep |
grep pattern file |
Unquoted $var |
"$var" always |
[ ] for complex tests |
[[ ]] |
| Backtick substitution | $(command) |
$? check after command |
if command; then |
echo for debug |
printf '%s\n' (portable) |
No set -euo pipefail |
always set at script top |
| Temp files without cleanup | trap 'rm -f "$tmp"' EXIT |
eval with user input |
avoid; use arrays for dynamic commands |
#!/bin/sh with Bash features |
#!/usr/bin/env bash |
String math expr 1 + 1 |
$(( 1 + 1 )) |
test -z for number comparison |
(( )) for arithmetic |
Limitations
- These are language-specific guidelines and do not cover overall architectural decisions.
- Over-compression might reduce readability; apply judgement.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.