> your AI agent picks dependencies from memory; give it dated facts — try starlog.dev ↗ vet your agent's deps ↗ vibe-coding is fine. vibe-importing isn’t. — try starlog.dev ↗ vibe-importing isn’t fine ↗ your agent has never seen your private packages — try starlog.dev ↗ facts for private packages ↗ a linter for the dependencies your AI agent picks — try starlog.dev ↗ a linter for agent deps ↗

Back to Articles

The Pure Bash Bible: Eliminating External Dependencies One Built-in at a Time

[ View on GitHub ]

The Pure Bash Bible: Eliminating External Dependencies One Built-in at a Time

Hook

Every time you call grep in a bash script, you spawn a new process. Do this in a loop 10,000 times, and you've just created 10,000 processes when zero would suffice.

Context

Traditional Unix philosophy celebrates small, composable utilities that do one thing well. Tools like grep, sed, awk, and cut have been the workhorses of shell scripting for decades, and for good reason—they're powerful, well-tested, and expressive. But this elegance comes with a hidden cost: process creation overhead.

Every external command spawns a new process, which means forking, loading the executable, executing it, and collecting results through pipes. In a script that runs once to process a log file, this overhead is negligible. But in tight loops, container initialization scripts, embedded systems with limited resources, or high-performance automation, these microseconds compound into seconds or even minutes. Moreover, external dependencies introduce portability problems—different systems have different versions of utilities with subtly different behaviors. The pure-bash-bible emerged from this tension: what if we could accomplish common text processing and system tasks using only bash's built-in features, eliminating both the performance penalty and the dependency complexity?

Technical Insight

Core Categories

Quality Assurance

consults

provides

organized into

validated by

verified by

parameter expansion

built-in operations

native bash I/O

optimized patterns

includes

pure bash alternatives

Script Developer

Bash Reference Guide

Code Snippets Library

Categorized Functions

Shellcheck Linting

Unit Tests

String Operations

Array Manipulation

File Handling

Loop Patterns

System architecture — auto-generated

The core insight of the pure-bash-bible is that bash's parameter expansion syntax is far more powerful than most developers realize. Consider a common task: trimming whitespace from a string. The traditional approach pipes to external utilities:

# Traditional approach with external processes
trimmed=$(echo "  hello world  " | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')

The pure-bash alternative uses parameter expansion patterns:

# Pure bash approach
string="  hello world  "
# Trim leading whitespace
string="${string#"${string%%[![:space:]]*}"}"
# Trim trailing whitespace
string="${string%"${string##*[![:space:]]}"}" 

This looks cryptic at first, but it's executing entirely within the bash process. The ${parameter#pattern} syntax removes the shortest match from the beginning, while ${parameter##pattern} removes the longest match. By nesting these expansions, we can extract the whitespace and then remove it. Zero external processes spawned.

The performance difference becomes dramatic in loops. The repository includes benchmarks showing that pure-bash string operations can be 10-100x faster than their external equivalents when called repeatedly. This isn't micro-optimization—it's the difference between a script that completes in seconds versus minutes.

Another powerful category is array manipulation without awk or cut. Suppose you need to split a string on a delimiter:

# Traditional approach
IFS=',' read -ra parts <<< "apple,banana,cherry"
#parts is now an array: (apple banana cherry)

# Or for extracting specific fields like cut
string="apple,banana,cherry"
IFS=',' read -r _ second _ <<< "$string"
# second="banana"

The read built-in with IFS manipulation eliminates the need for cut or awk for simple field extraction. For more complex operations, bash's built-in regex matching can replace grep:

# Check if string matches pattern (replaces grep -q)
if [[ $string =~ ^[0-9]+$ ]]; then
    echo "String is numeric"
fi

# Extract matched groups (replaces sed/awk capture groups)
if [[ $email =~ ^([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,})$ ]]; then
    username="${BASH_REMATCH[1]}"
    domain="${BASH_REMATCH[2]}"
    tld="${BASH_REMATCH[3]}"
fi

The BASH_REMATCH array automatically captures regex groups, providing functionality equivalent to sed or awk capture groups without spawning processes.

The repository's architecture reflects its purpose as a reference guide rather than a library. Each snippet is self-contained, documented with usage examples, and validated through shellcheck linting and unit tests. This makes it suitable for copy-paste integration into existing scripts. The organization by category (strings, arrays, file operations, loops) mirrors how developers actually search for solutions: "How do I reverse a string in bash?" rather than "What can parameter expansion do?"

One often-overlooked category is file operations. Reading files line-by-line typically involves cat piped to while read, but bash can read files directly:

# Traditional (spawns cat process)
cat file.txt | while read -r line; do
    echo "$line"
done

# Pure bash (no external process)
while IFS= read -r line; do
    echo "$line"
done < file.txt

The difference is subtle but significant: input redirection uses bash's built-in file handling rather than spawning cat. In scripts that process many files, this eliminates thousands of unnecessary process creations.

Gotcha

The pure-bash approach has real tradeoffs that the repository honestly acknowledges. First, readability suffers. A developer familiar with Unix utilities can immediately understand echo "$string" | sed 's/foo/bar/g', but the pure-bash equivalent ${string//foo/bar} requires knowledge of parameter expansion syntax that isn't universal. If you're writing scripts that will be maintained by a team with varying bash expertise, the external utility approach may be more maintainable despite its performance cost.

Second, these techniques are bash-specific and won't work in POSIX sh or other shells like dash, which is the default /bin/sh on Debian and Ubuntu systems. If your script needs to run on minimal systems or you're writing for strict POSIX compliance, most of these techniques are unavailable. The repository does mention the companion pure-sh-bible project, but the POSIX alternatives are even more limited.

Third, some operations genuinely are clearer and more maintainable with external utilities. Complex text transformations that would require convoluted parameter expansion might be expressed cleanly in a single awk or sed command. The repository provides the alternatives, but doesn't always advocate for them—sometimes spawning a process is the right choice. There's also a ceiling to the complexity pure bash can handle elegantly; if you're doing serious text processing, a language like Python or Perl might be more appropriate than either bash built-ins or traditional utilities.

Finally, the performance benefits only materialize in specific scenarios. A single invocation of an external utility in a script that runs once per day won't benefit from a pure-bash rewrite. The gains come from repeated operations, tight loops, or resource-constrained environments. Premature optimization applies here too—profile before you refactor.

Verdict

Use if: You're writing performance-critical bash scripts with tight loops that call external utilities repeatedly, building container initialization scripts where every millisecond matters, working in embedded systems or resource-constrained environments where minimizing external dependencies is crucial, or you need portable scripts that work across systems with different utility versions. Also use this as a learning resource to deepen your understanding of bash's advanced features—many of these techniques are genuinely useful to know even if you don't always apply them. Skip if: You're writing simple one-off scripts where readability trumps performance, your team lacks deep bash expertise and maintainability is paramount, you need POSIX sh compatibility and can't rely on bash-specific features, or you're doing complex text processing that would be clearer in awk, sed, or a higher-level language. Also skip if you haven't profiled your scripts to confirm that external process overhead is actually your bottleneck—don't optimize prematurely.