Executable documentation for shell commands in Markdown.
md-exec keeps your documentation in sync with actual behavior.
It parses Markdown files, extracts shell commands from code blocks, executes them, and validates the output against expected results.
python3 md-exec.py [options] <path>
Arguments:
<path>— A Markdown file or directory. If a directory is provided, all.mdfiles are processed recursively.
Options:
--abort-on-fail— Stop immediately after the first test failure.--print-command-output— Stream command output to the console in real-time.
Use ... in expected output to match any text. This lets you ignore variable content like timestamps, IDs, or paths that change between runs.
Both stdout and stderr are captured together. This is essential for tools like Docker that print status messages to stderr.
- Environment variables persist across code blocks within the same file.
- Working directory changes via
cd,pushd, andpopdare tracked and preserved. - Docker works natively since container state is managed by the Docker daemon.
This documentation is itself a test suite for md-exec. Validate it with python3 md-exec.py md-exec.md.
A code snippet contains a shell command (prefixed with $) and its expected output:
$ echo Hola mundo
Hola mundoMultiple commands and outputs can be placed in the same snippet:
$ echo foo
foo
$ echo bar
barDefine an environment variable in one snippet...
$ export FOO=123...and use it in a later snippet:
$ echo $FOO
123Use ... to match any substring in the output:
$ echo The Width of a Circle
...Width...Circle...It works across newlines too:
$ printf "*** foo\n *** bar\n *** baz\n ***\n"
...foo...bar...baz...You can also use multiple matchers on separate lines:
$ printf "*** foo\n *** bar\n *** baz\n ***\n"
...foo...
...bar...
...baz...New lines before and after ... are discarded:
$ printf "foo"
...
foo
...Code blocks inside HTML comments are skipped.
Directory changes persist between commands. Both cd - (return to previous directory) and pushd/popd (directory stack) are supported:
$ cd /tmp
$ pwd
/tmp
$ cd -
...$ pushd /tmp
/tmp ...
$ pwd
/tmp
$ popdCode blocks inside HTML elements (like <details>) are detected and executed correctly:
Collapsable
```sh
$ echo spam spam spam
spam spam spam
```
Add options to the opening fence (e.g., ```sh {timeout=5}) or inline with the command.
Mark a test that is expected to fail with {expected_fail}:
$ true
1Check exit codes using echo $?:
$ true
$ echo $?
0$ false
$ echo $?
1Or use the {exit_code} option for a more concise syntax. Supported operators: ==, !=, >, <, >=, <=.
$ false$ falseAssert that a command produces no output (ignoring whitespace) with {empty_output}:
$ printf ""$ printf "\n \n"Use {strip} when the command output differs only in trailing newlines:
$ printf "\nfoo"
fooYou can also use {ignore_trailing_new_line} as an alias for {strip}.
Set a maximum execution time with {timeout=N}. If exceeded, the command is killed and the exit code is set to 1:
$ sleep 5
$ echo $?
1Heredoc syntax is supported for multi-line input:
$ cat > /tmp/md-exec.txt << 'EOF'
bla
blah
EOFAdd directives as HTML comments to affect all subsequent tests in a file:
<!-- md-exec: abort_on_fail=true, print_command_output -->abort_on_fail— Stop processing after the first failure.print_command_output— Print command output to the console as it runs.
These can also be set via CLI flags (--abort-on-fail, --print-command-output), which apply globally. In-file directives override CLI flags for tests that follow them.