Overview
The compilation process transforms 16 individual module files in src/ into a single executable file ready to be sourced in any Bash script. This process includes validation, linting with ShellCheck, and intelligent concatenation.
Basic compilation
Compile all modules into the default output file:
./main.sh compile
# Writing array.sh... ok
# Writing colour.sh... ok
# Writing device.sh... ok
# ...
# Compiled 16 file(s) to compiled.sh
Specify a custom output filename:
./main.sh compile myproject-stdlib.sh
# Compiled 16 file(s) to myproject-stdlib.sh
The compiled file is automatically made executable with chmod +x.
Compilation steps
The compile_files() function in main.sh follows a careful workflow:
Directory validation
Verify the source directory exists and contains module files. # Validate src directory exists
if [[ ! -d " $src_dir " ]]; then
echo "Error: src directory not found: $src_dir " >&2
return 1
fi
File collection
Gather all .sh files from the source directory and validate at least one exists. # Collect .sh files upfront so we can validate before touching output
local -a files = ()
for f in " $src_dir "/*.sh ; do
[[ -f " $f " ]] && files += ( " $f " )
done
if (( ${ # files [ @ ]} == 0 )); then
echo "Error: No .sh files found in $src_dir " >&2
return 1
fi
Output initialization
Create/truncate the output file only after confirming source files exist. # Truncate/create output only after we know there's something to write
true > " $output_file "
ShellCheck integration
Run ShellCheck on each module to detect errors, warnings, and style issues. local has_shellcheck = false
command -v shellcheck > /dev/null 2>&1 && has_shellcheck = true
For each file: if $has_shellcheck ; then
local sc_out
sc_out = $( shellcheck --format=gcc " $func_file " 2> /dev/null )
err_file = $( echo " $sc_out " | grep -c ': error:' )
warn_file = $( echo " $sc_out " | grep -c ': warning:' )
info_file = $( echo " $sc_out " | grep -c ': note:' )
# Also show human-readable output
shellcheck --color=auto --format=tty " $func_file " 2> /dev/null || true
local file_issues = $(( err_file + warn_file + info_file ))
if (( file_issues > 0 )); then
issue_str_file = " — $file_issues issues ( $err_file errors, $warn_file warnings, $info_file info)"
(( total_err += err_file ))
(( total_warn += warn_file ))
(( total_info += info_file ))
fi
fi
Shebang handling
Strip shebang lines from all files except the first to avoid multiple #!/usr/bin/env bash declarations. local i_line = 0
while IFS = read -r line ; do
# Strip shebang from all but the first file
if (( i > 0 && i_line == 0 )); then
(( i_line ++ ))
[[ " $line " =~ ^# ! ]] && continue
fi
printf '%s\n' " $line " >> " $output_file "
(( i_line ++ ))
done < " $func_file "
Final report
Display compilation summary with total file count and ShellCheck statistics. chmod +x " $output_file " 2> /dev/null
local total_issues = $(( total_err + total_warn + total_info ))
local final_issue_str = ""
if (( total_issues > 0 )); then
final_issue_str = " — $total_issues total issues ( $total_err errors, $total_warn warnings, $total_info info)"
fi
echo "Compiled $i file(s) to $output_file ${ final_issue_str }"
Custom builds
You can create custom builds by removing modules you don’t need from the src/ directory before compiling.
Never remove runtime.sh — all other modules depend on it for environment detection.
Example: Minimal string-only build
Create a temporary directory
mkdir -p custom-build/src
Copy only required modules
cp src/runtime.sh custom-build/src/
cp src/string.sh custom-build/src/
Compile the custom build
cd custom-build
./main.sh compile string-only.sh
# Writing runtime.sh... ok
# Writing string.sh... ok
# Compiled 2 file(s) to string-only.sh
This produces a lightweight build with only runtime and string functions (~1,200 lines vs. ~7,500 full build).
Build verification
After compilation, verify the build with the statistics command:
./main.sh stat ./compiled.sh
Example output:
=== bash::framehead.sh Diagnostics ===
File size: 7565 lines // 234 KiB
First 5 lines:
#!/usr/bin/env bash
runtime::is_terminal () {
[[ -t 0 && -t 1 && -t 2 ]]
}
...
Last 5 lines:
pm::update () {
pm::run update
}
=== Testing load time in fresh shell ===
Real: 0.08 s, User: 0.07 s, Sys: 0.01 s
=== Function count by module ===
115 string
79 fs
74 timedate
...
785 total functions loaded
Load time under 0.1 seconds on modern hardware makes the framework suitable for both interactive and automated scripts.
Quality assurance
ShellCheck integration
ShellCheck runs automatically during compilation to catch:
Syntax errors — Invalid Bash syntax
Logic errors — Unreachable code, incorrect comparisons
Style warnings — Quote issues, deprecated syntax
Portability notes — Bash-specific vs. POSIX features
Example output:
In src/string.sh line 42:
[[ $1 =~ ^[0-9]+$ ]]
^-- SC2086: Double quote to prevent word splitting
Writing string.sh... ok — 1 issues (0 errors, 1 warnings, 0 info )
Error handling
Compilation fails gracefully with clear messages:
# Nothing was actually written (all files were empty)
if (( i == 0 )); then
echo "Error: All source files were empty, output not written" >&2
rm -f " $output_file "
return 1
fi
Distribution
Once compiled, the output file is completely self-contained and portable:
Direct sourcing
#!/usr/bin/env bash
source /path/to/compiled.sh
string::upper "hello world" # HELLO WORLD
math::factorial 5 # 120
Relative sourcing
For scripts that travel with the framework:
#!/usr/bin/env bash
source "$( dirname " $0 ")/compiled.sh"
# Now all functions are available
Embedding in projects
Copy the compiled file directly into your project:
cp compiled.sh ~/myproject/lib/stdlib.sh
Then source it in your scripts:
source "$( dirname " $0 ")/lib/stdlib.sh"
Rename the compiled output to match your project conventions — the filename doesn’t affect functionality.
Testing the build
Run the comprehensive test suite on any compiled file:
./main.sh test ./compiled.sh
Example output:
=== bash::framehead functional smoke tests ===
--- string ---
PASS string::upper
PASS string::lower
PASS string::length
PASS string::contains (true)
...
--- array ---
PASS array::length
PASS array::first
...
--- math ---
PASS math::abs (negative)
PASS math::factorial
...
=== Results: 659 passed, 0 failed, 8 skipped, 1 untested ===
=== Success rate: 100.0% (659/659) ===
Tests are skipped when optional dependencies (like bc or openssl) are unavailable:
SKIP math::bc (bc not available )
SKIP hash::hmac (openssl not available )
Build optimization
Minimize file size
Remove unused modules to reduce the compiled file size:
Modules included Line count File size All 16 modules 7,565 234 KiB runtime + string + array + math ~2,700 ~85 KiB runtime + string only ~1,200 ~38 KiB runtime only ~330 ~11 KiB
Load time scales linearly — Each module adds ~5ms load time
Memory footprint is negligible — Function definitions are lightweight
No runtime overhead — Functions aren’t invoked until called
Even the full 785-function build loads in under 100ms, making it suitable for scripts that run frequently.
Troubleshooting
Compilation fails with “No .sh files found”
Ensure you’re running the compile command from the project root:
cd bash-framehead
./main.sh compile
ShellCheck warnings about word splitting
These are usually safe to ignore in the framework context, but fix them for cleaner builds:
# Before
[[ $var =~ pattern ]]
# After
[[ " $var " =~ pattern ]]
Custom build missing functions
Verify you included runtime.sh — all modules depend on it:
ls custom-build/src/
# Must include runtime.sh
Next steps
Architecture Understand the framework design
Module system Explore available modules
Naming conventions Master the function naming pattern
Development Learn how to add custom modules