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