Up to Main Index                              Up to Journal for June, 2026

                     JOURNAL FOR SATURDAY 13TH JUNE, 2026
______________________________________________________________________________

SUBJECT: Gluing Files Together: inc — a format-agnostic include tool in Bash
   DATE: Sat 13 Jun 22:59:59 BST 2026


  TL;DR Generic include tool written in Bash: ./inc


Recently I’ve been working on a large Markdown document. About 3,000 lines
plus 14 includes. The includes consisting of scripts, YAML manifests, INI
configuration files. As the document progressed the included files kept
changing and I needed to delete the copy in the document and replace it. This
became very tedious very quickly.

I went looking for a simple generic include utility or tool. My requirements
were simple. Replace a reference such as `{inc: ./my.yaml}` with its content.

I didn't find anything. There were huge, complex pre-processors, but nothing
simple. So I wrote `inc`. A Bash script with about 70 lines of code, 65 lines
of comments. It can take a 3,000 line Markdown file with 14 includes and spit
out a combined 5,550 line Markdown file in 117ms. It also has a few tricks.

Starting with a basic example, using the default directive `{inc: <file>}`:


    text1.txt:
      The quick brown
      {inc: text2.txt}

    text2.txt
      fox jumps over
      {inc: text3.txt}

    text3.txt
      the lazy dog.


Running `inc` on `text1.txt` produces:


    >inc < text1.txt
    The quick brown
    fox jumps over
    the lazy dog.


If we modify `text1.txt` and `text2.txt` to indent their include:


    text1.txt:
      The quick brown
        {inc: text2.txt}

    text2.txt
      fox jumps over
        {inc: text3.txt}


When we re-run we see that the indentation is relative for included files:


    >inc < text1.txt
    The quick brown
      fox jumps over
        the lazy dog.


Because `text1.txt` indented the include for `text2.txt`, the indentation was
kept when including `text3.txt`. We can also set an initial indent, which need
not be just white-space. For example:


    >INDENT="| " inc < text1.txt
    | The quick brown
    |   fox jumps over
    |     the lazy dog.


If the default directive `{inc: <file>}` clashes with existing file content,
the directive can be overridden by setting PREFIX and SUFFIX. For example, to
use `#INC <file>#` instead:


    text1.txt:
      The quick brown
      #INC text2.txt#

    text2.txt
      fox jumps over
      #INC text3.txt#

    text3.txt
      the lazy dog.


We then run `inc`:


    >PREFIX="#INC" SUFFIX="#" inc < text1.txt
    The quick brown
    fox jumps over
    the lazy dog.


When writing a document directives may be left blank `{inc: }` and will
silently be skipped. This can be useful as a placeholder when scaffolding out
a document before the files to be included are written.

Relative paths within include directives are evaluated relative to the file
containing the directive. For example:


    text1.txt:
      In text1...
      {inc: A/text1.txt}

    A/text1.txt:
      In A/text1...
      {inc: ../B/text1.txt}

    B/text1.txt:
      In B/text1...
      {inc: text2.txt}

    B/text2.txt:
      The prize!


Our directory structure is:


    .
    |-- A
    |   `-- text1.txt
    |-- B
    |   |-- text1.txt
    |   `-- text2.txt
    `-- text1.txt



When we run `inc` it produces:


    >inc < text1.txt
    In text1...
    In A/text1...
    In B/text1...
    The prize!


There is no ambiguity over which `text1.txt` should be included at any point
in the execution chain. Under the hood, `inc` anchors its cycle-tracking
safety checks to physical filesystem directory inodes, making it completely
immune to complex relative path loops or symlink traps.

Under stress testing `inc` has handled over 500 nested includes and over 500
nested directories. Due to process-forking overhead `inc` slows down a tad at
that extreme depth, but its memory footprint stays perfectly flat and it
handles the pathological workload safely.

Testing more realistically, for 100 nested includes `inc` takes 1.549s to run.
For 100 nested directories with an include file in each directory `inc` takes
1.458s to run. In both cases over 100 includes are being processed.

As a generic include tool `inc` is completely format-agnostic. It works
seamlessly with plain text, Markdown, JSON, YAML, XML, and source code. This
is incredibly useful when a deployment pipeline or a static site generator
expects a single monolithic file, but you want to maintain your sanity by
breaking it down into manageable components.

Taking Markdown as an example, `inc` can stitch together structural book
chapters while pulling dynamic configuration manifests directly into fenced
code blocks:


    # Project Deployment Guide

    Here is the exact production configuration we use for our cluster:

    ```yaml
    {inc: ./deploy/manifest.yaml}
    ```

    Please ensure the keys match before running the setup script.


Because `inc` preserves indentation, the entire contents of `manifest.yaml`
will be injected into that Markdown file perfectly indented inside the fenced
block.

`inc` also treats standard input and standard output as first-class citizens,
making it a natural fit for classic Unix pipelines:


    # Preview a fully stitched document on the fly without writing to disk
    inc < main.md | less

    # Lint a composite configuration file before committing it
    inc < server.conf | nginx -t -c /dev/stdin

    # Dynamically pull headers and footers around a raw data stream
    cat body.txt | INDENT="  " inc | cat header.txt - footer.txt > index.html


If you need to support multiple distinct types of include tags simultaneously,
`inc` is lean enough to pipe straight into itself for multi-pass compilation:


    # Pass 1 resolves standard templates; pass 2 injects runtime config vars
    inc < template.txt | PREFIX="{" SUFFIX="}" inc > build.conf


Ultimately, inc handles the tedious work of text assembly without forcing you
to adopt heavy, opinionated framework pre-processors. It does one thing, does
it cleanly, and stays entirely out of your way.

Download a copy, play with it, hack on it and make it your own: ./inc

--
Diddymus


  Up to Main Index                              Up to Journal for June, 2026