Skip to main content

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:
1

Directory validation

Verify the source directory exists and contains module files.
main.sh:8-12
# Validate src directory exists
if [[ ! -d "$src_dir" ]]; then
    echo "Error: src directory not found: $src_dir" >&2
    return 1
fi
2

File collection

Gather all .sh files from the source directory and validate at least one exists.
main.sh:14-23
# 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
3

Output initialization

Create/truncate the output file only after confirming source files exist.
main.sh:25-26
# Truncate/create output only after we know there's something to write
true > "$output_file"
4

ShellCheck integration

Run ShellCheck on each module to detect errors, warnings, and style issues.
main.sh:30-31
local has_shellcheck=false
command -v shellcheck >/dev/null 2>&1 && has_shellcheck=true
For each file:
main.sh:44-61
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
5

Shebang handling

Strip shebang lines from all files except the first to avoid multiple #!/usr/bin/env bash declarations.
main.sh:66-75
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"
6

Final report

Display compilation summary with total file count and ShellCheck statistics.
main.sh:88-96
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

1

Create a temporary directory

mkdir -p custom-build/src
2

Copy only required modules

cp src/runtime.sh custom-build/src/
cp src/string.sh custom-build/src/
3

Copy the main script

cp main.sh custom-build/
4

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:
main.sh:101-118
=== 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:
main.sh:82-86
# 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 includedLine countFile size
All 16 modules7,565234 KiB
runtime + string + array + math~2,700~85 KiB
runtime + string only~1,200~38 KiB
runtime only~330~11 KiB

Performance considerations

  • 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

Build docs developers (and LLMs) love