A general-purpose bash script minifier with optional source bundling and variable obfuscation.
minifier -i <INPUT> -o <OUTPUT> [OPTIONS]
Options:
-i <file> Input bash script
-o <file> Output file
-B, --bundle Enable source bundling (resolve and inline imports)
-S, --search-path <dir> Search directory for resolving imports (repeatable), requires -B
-O, --obfuscate Enable variable name obfuscation
-V <pattern> Exclude variables matching pattern from obfuscation (repeatable), requires -O
-I <patterns> Ignore variables matching regex (comma-separated, default: "usage,args"), requires -O
-h, --help Print help
minifier -i script.sh -o script.min.shminifier -i script.sh -o script.min.sh -B -S ./librariesminifier -i script.sh -o script.min.sh -Ominifier -i script.sh -o script.min.sh -B -S ./libraries -Ominifier -i script.sh -o script.min.sh -O -V MYVAR -V CONFIG_ -I "usage,args"The minifier processes input through up to 5 phases:
Input → Bundle (if -B) → Strip → Flatten → Obfuscate (if -O) → Join → Output
Resolves import, source, and . (dot-source) statements, recursively inlining
referenced files to produce a single self-contained bash script.
| Pattern | Example |
|---|---|
import <target> |
import fmt, import @core/utils |
source <path> |
source ./lib.sh |
. <path> |
. ./helper.sh |
Lines with $ variable expansions in the path (e.g. source "${CONFIG}") are left as-is.
For each import target (after stripping @/~ prefix):
- Relative to the directory of the file containing the import
- Each
--search-pathdirectory, in CLI order - For each candidate, tries: as-is,
.sh,.bash
Unresolvable imports are left as-is (strip phase removes import lines later).
| Context | Behavior |
|---|---|
| Top-level (brace depth == 0) | Dedup: skip if file already inlined |
| Inside function body (brace depth > 0) | Always inline — content is scoped |
# minifier force source annotation |
Always inline regardless |
# Top-level: inlined once, second import is deduped
import shared
import shared # skipped
# Inside function: always inlined (scoped)
foo() {
import shared # inlined again
}
# Force annotation: overrides dedup at top level
# minifier force source
import shared # inlined even if already seenRemoves entire lines that are unnecessary in minified output:
- Full-line comments (including shebangs)
- Blank / whitespace-only lines
import <word>calls and single-lineimport() { ... }definitionsset -euo pipefail- Mid-line shebangs from file concatenation (
}#!/usr/bin/env bash→ split)
Per-line cleanup:
- Remove leading whitespace (indentation)
- Remove trailing standalone
;(but not;;) - Remove end-of-line comments (
# ...not inside quotes)
Discovers local variables from 8 patterns:
| Pattern | Example |
|---|---|
| Assignment | var=value |
| Local | local var / local -a var |
| Read | read -r var |
| For | for var in ... |
| Array | var[0]=value |
| Pre-increment | (( ++var )) |
| Post-increment | (( var++ )) |
| Declare (skipped) | declare on same line suppresses discovery |
Renames variables using 21 substitution rules across assignments, $var, ${var},
parameter expansions, arithmetic contexts, arrays, and more — while respecting
single-quote boundaries.
Place # obfus ignore variable on the line before a declaration to exclude
it from obfuscation:
# obfus ignore variable
local keep_this_name="important"Aggressively joins newlines into single-line output while preserving:
- Heredoc content (verbatim between
<<DELIM...DELIM) - Case statement structure (bash requires newline after
in) - Multi-line arrays, quoted strings, backslash continuations
Fixes then;/do;/else; → then /do /else (improvement over Perl predecessor).
cargo build --release
# Binary: target/release/minifier# Unit + integration tests
cargo test
# Code coverage (requires llvm-tools: rustup component add llvm-tools)
# Run via the project helper:
# argsh coverage minifiersrc/
├── main.rs CLI + pipeline orchestration
├── bundle.rs Source-file bundling (recursive import inlining)
├── strip.rs Line-level stripping (comments, blanks, imports)
├── flatten.rs Per-line cleanup (indentation, trailing ;, EOL comments)
├── discover.rs Variable discovery from bash source (8 patterns)
├── obfuscate.rs Variable renaming (21 substitution rules per variable)
├── join.rs Aggressive newline joining (heredoc/case/array/quote-aware)
└── quote.rs Quote-tracking for multi-line string detection