Shellkin is a Gherkin-style BDD test framework for command-line tools and shell scripts. It is built with Bashly and distributed as a single Bash script with no runtime dependencies.
It lets you write feature files such as:
Feature: --help
Show help message
Scenario: Run --help
When I run 'shellkin --help'
Then the output should include 'shellkin [TARGET] [OPTIONS]'and back them with shell step definitions:
@When I run '{command}'
run "$command"
@Then the output should include '{text}'
[[ "$LAST_STDOUT" == *"$text"* ]]Indenting the step body is recommended for readability, but optional.
This setup script will download the latest shellkin release executable as well as the man pages.
curl -Ls get.dannyb.co/shellkin/setup | bashFeel free to inspect the setup script before running.
Download the shellkin bash script from the
latest release, place
it in your path and make it executable.
# download the latest release and place it in /usr/local/bin
wget https://get.dannyb.co/shellkin
sudo install -m 0755 shellkin /usr/local/bin/Shellkin is currently usable for local feature testing and dogfoods itself
through the repository features/ directory.
Implemented pieces include:
- feature discovery, validation, filtering, and scenario selection
- English Gherkin features, rules, backgrounds, scenarios, and outlines
- steps, tags, comments, doc strings, and data tables
- step definitions with named placeholders and lifecycle hooks
- optional support script loading
- fail-fast execution, deferred cleanup, and scenario summaries
| Feature | Status |
|---|---|
Feature |
Supported |
| Feature description text | Supported |
Rule |
Supported |
Background |
Supported |
Scenario |
Supported |
Scenario Outline |
Supported |
Examples |
Supported |
Given, When, Then |
Supported |
And , But |
Supported |
* step keyword |
Supported |
Doc strings (""") |
Supported |
Comments (#) |
Supported |
Tags (@tag) |
Supported |
Before, After hooks |
Supported |
BeforeAll, AfterAll hooks |
Supported |
| Data tables | Supported |
Shellkin intentionally implements a compact English Gherkin dialect. Keyword aliases and localization, multiple Examples blocks, tags on Examples blocks, and escaped data-table cells are not currently supported.
Tags can be selected with --tag / -t and skipped with --exclude-tag / -x.
@Before and @After hooks are declared in step definition files and may be
limited to a tag. @BeforeAll and @AfterAll hooks are untagged and wrap the
selected scenario run.
# Create a starter features directory:
shellkin --init
# Run all repo features:
shellkin
# Validate feature and step definition files without executing steps:
shellkin --validate
# Stop after the first failing scenario:
shellkin --fail-fast
# Run scenarios tagged @smoke:
shellkin -t @smoke
# Skip scenarios tagged @slow:
shellkin -x @slow
# Run a specific directory:
shellkin path/to/features
# Run a single feature file:
shellkin path/to/features/example.featureWhen a step fails, the remaining steps in that scenario are marked as skipped and are not executed.
Use shellkin --validate to check feature structure and step-definition matching
without running any step bodies.
Use shellkin --init to create a starter features directory with an example
feature, step definitions, support.sh, and a local README. Pass a target
directory or configure --default-target to initialize a directory other than
features; use --stepdefs to choose the step definition directory name.
Shellkin supports configuration from a .shellkin argfile in the current
working directory.
This is useful for project-level defaults such as the default target, step-definitions directory, and support files:
--default-target tests
--stepdefs steps
--load first_support.sh
--load second_support.sh
The argfile format is intentionally simple:
- Only lines that start with
-or--are considered - Non-flag lines are ignored
- Unknown flags are ignored
- A flag value must appear on the same line as the flag
- Matching outer quotes are stripped
This repository also provides a Codex skill for AI agents that need to write Shellkin tests in user projects.
Use the built-in installer skill.
In Codex chat, use this prompt:
install the skill from https://github.com/DannyBen/shellkin/tree/main/skills/shellkin
(master branch)
Claude Code supports project and user skill locations:
- Project skill:
.claude/skills/shellkin/SKILL.md - User skill:
~/.claude/skills/shellkin/SKILL.md
Copy skills/shellkin from this repo into one of those locations.
Shellkin expects this structure:
features/
├── step_definitions/
│ └── core.sh
├── support.sh
└── example.feature
- Feature files live in the target directory.
- Step definitions live in
step_definitions/under that same directory. support.shis loaded automatically when present.- Additional support files are loaded when passed with
--load. --stepdefsand--loadpaths are relative to the features directory.
You can create this structure with:
shellkin --initStep definitions are shell snippets declared in files under
step_definitions/.
@When I run '{command}'
run "$command"
@Then the output should include '{text}'
[[ "$LAST_STDOUT" == *"$text"* ]]Each step definition starts with a header line:
@Given ...
@When ...
@Then ...
The lines that follow are the step body and are executed when the step matches. Indenting the body is recommended for readability, but optional.
Definition headers can use named tokens in braces. When a step matches, each token becomes an exported shell variable available to the body:
@Then the file '{path}' should exist
[[ -f "$path" ]]Token names must start with a letter or underscore, and may contain letters, numbers, and underscores.
Quoted tokens accept either quote delimiter when the step runs. For example, this definition:
@Then the text should include '{text}'matches both of these steps, and captures the text without the outer quotes:
Then the text should include "Something's wrong"
Then the text should include 'Jim "Jimbo" Jackson'The opening and closing quote in the feature step must match. Quoted token patterns do not match unquoted values.
Each definition continues until the next step or hook header or the end of the file.
Step definition files can also declare hooks.
@BeforeAll
./suite-setup
@Before
mkdir -p tmp
@After
rm -rf tmp
@Before @needs-server
./server start
@After @needs-server
./server stop
@AfterAll
./suite-teardownHooks without a tag run for every scenario. Tagged hooks run only for scenarios
with that tag, including tags inherited from the feature. @Before hooks run
before background and scenario steps. @After hooks run after the scenario
steps, even when a step or @Before hook fails.
@BeforeAll runs once before the first selected scenario executes. @AfterAll
runs once after the last selected scenario executes. If no scenario is selected,
neither all-hook runs. All-hooks do not accept tags. If @BeforeAll fails, the
run aborts immediately and @AfterAll does not run. If @AfterAll fails, the
run fails.
Hooks can call helper functions from support.sh:
# features/support.sh
start_server() {
./server start
}
stop_server() {
./server stop
}# features/step_definitions/hooks.sh
@Before @needs-server
start_server
@After @needs-server
stop_serverShellkin currently provides these built-in helpers for step definitions:
Use run to execute a shell command while capturing its result for later
assertions.
@When I run '{command}'
run "$command"run always returns success, even if the command fails. Inspect the captured
result through the environment variables described below.
Use fail to fail the current step with an optional custom message.
@Then the output should include '{text}'
[[ "$LAST_STDOUT" == *"$text"* ]] || fail "invalid output detected"Use defer to register cleanup code that should run when the current scenario
finishes.
@Given I am in a temp directory
old_pwd=$PWD
temp_dir=$(mktemp -d)
cd "$temp_dir"
defer cd "$old_pwd"
defer rm -rf "$temp_dir"Deferred actions are scenario-scoped, run after both passing and failing scenarios, and execute in reverse order of registration.
Shellkin exposes these variables to step definition bodies:
| Variable | Meaning |
|---|---|
LAST_EXIT_CODE |
Exit status captured by the most recent run call |
LAST_STDOUT |
Standard output captured by the most recent run call |
LAST_STDERR |
Standard error captured by the most recent run call |
DOC_STRING |
Doc string attached to the current step, if any |
TABLE_HEADER |
Header cells from the current step's data table array |
TABLE_ROWS |
Tab-separated data rows from the current step's table |
Example:
@Then the output should match
[[ "$LAST_STDOUT" == "$DOC_STRING" ]]For real-world examples, see this repository's features directory and the rush features.
If you used the setup script, you can run this uninstall script:
curl -Ls get.dannyb.co/shellkin/uninstall | bashIf you experience any issue, have a question or a suggestion, or if you wish to contribute, feel free to open an issue.
