Frost2o24/bash-instruct-55k
Bash Instruct I — 55,000 verified natural-language → Bash pairs ⚠️ Superseded by Bash Instruct III III doubles the utility vocabulary (89 → 182), adds grouped equivalent answers, real human phrasing from tldr-pages, validation by execution on real Linux, and a style pass that removes shell antipatterns. New work should use III. This version remains useful for one specific purpose: it overlaps III by only ~29%, so it is the one generation that can be deduplicated… See the full description on the dataset page: https://huggingface.co/datasets/Frost2o24/bash-instruct-55k.
Bash Instruct I — 55,000 verified natural-language → Bash pairs
### ⚠️ Superseded by **Bash Instruct III** III doubles the utility vocabulary (89 → 182), adds grouped equivalent answers, real human phrasing from tldr-pages, validation by execution on real Linux, and a style pass that removes shell antipatterns. New work should use III. This version remains useful for one specific purpose: it overlaps III by only ~29%, so it is the one generation that can be deduplicated in as extra volume. See Combining versions.
Bash Instruct I is the first-generation synthetic instruction-tuning dataset in this family, pairing natural-language requests with correct Bash: single commands, short pipelines, and multi-line scripts. It is built for supervised fine-tuning of small LLMs that must turn a plain request into shell code that actually runs.
Every row is a three-turn chat conversation (system / user / assistant) plus two metadata fields (category, utility) for slicing and analysis.
from datasets import load_dataset
ds = load_dataset("Frost2o24/bash-instruct-55k", split="train")Version comparison
This version is validated statically only — bash -n, ShellCheck, and the argument-fidelity gate. It was never executed at scale on real Linux, and it has a narrower utility vocabulary. Prefer III.
At a glance
All figures below are recomputed directly from the shipped `bash_dataset.jsonl`, not carried over from an earlier run.
Note the trade-off against later versions: request text here is more unique (98.7% vs ~80% of rows) because there are no grouped answer variants, but command text is less unique (75.6% vs 78.7%) because the same command recurs under different phrasings.
Quickstart
Load
from datasets import load_dataset
ds = load_dataset("Frost2o24/bash-instruct-55k", split="train")
print(ds)
# Dataset({features: ['messages', 'category', 'utility'], num_rows: 55000})
print(ds[0]["messages"])Fine-tune (TRL SFTTrainer)
The messages column is already in the OpenAI-style chat format that TRL, Axolotl, Unsloth, and LLaMA-Factory consume directly — no preprocessing or column mapping needed.
from datasets import load_dataset
from trl import SFTTrainer, SFTConfig
ds = load_dataset("Frost2o24/bash-instruct-55k", split="train")
trainer = SFTTrainer(
model="Qwen/Qwen2.5-Coder-1.5B-Instruct",
train_dataset=ds, # `messages` is auto-detected
args=SFTConfig(
output_dir="bash-sft",
max_length=1024,
num_train_epochs=2,
per_device_train_batch_size=8,
),
)
trainer.train()Analyze and slice
import collections
print(collections.Counter(ds["category"]))
print(collections.Counter(ds["utility"]).most_common(15))
scripts = ds.filter(lambda r: r["category"] == "script")There is no variant_group in this version, so a plain random row split is safe — every row is a distinct (request, command) pair.
Inspect without Python
head -n 1 bash_dataset.jsonl | jq .
jq -r '.utility' bash_dataset.jsonl | sort | uniq -c | sort -rn | head -20
jq -r '.category' bash_dataset.jsonl | sort | uniq -cDataset structure
One JSON object per line in bash_dataset.jsonl:
{
"messages": [
{"role": "system", "content": "You are a Bash expert."},
{"role": "user", "content": "Show the last 20 lines of error.log."},
{"role": "assistant", "content": "tail -n 20 error.log"}
],
"category": "single",
"utility": "tail"
}Verified schema integrity: all 55,000 lines parse as JSON, all carry the same three keys, all have exactly the system/user/assistant role order, and no message content is empty.
Categories
What's in the scripts
Scripts are short, safe-by-default operational snippets — not large programs. trap and here-doc usage is deliberately rare, and there are no function definitions.
System & info utility coverage
Small models routinely fumble the observability and process-management tools. These carry enforced minimum counts with correct, idiomatic flag usage:
uptime and hostname are lower because their genuine idiomatic command space is small; they are covered with real flag variants rather than padded with synonymous phrasings.
System prompts
Five equivalent Bash-assistant system prompts are rotated near-uniformly (~11.0k rows each) so the model does not overfit one string.
Quality assurance
1. Syntax — bash -n on every command
0 failures out of 55,000 (100% pass), verified with GNU bash 5.2.37 on Debian.
2. Static analysis — ShellCheck at warning level
On a 6,000-row random sample (ShellCheck 0.10.0): 97.95% of commands are completely clean. 123 rows produced a finding, almost all of them a single style nit:
All findings are warning level; there are no ShellCheck errors. The SC2010 pattern is fixed at the generator level in Bash Instruct III.
3. Argument fidelity
Every pair is gated so the command actually references the concrete nouns the request names — the same filename, extension, user, group, service, process, and port. The shipped file has 0 mismatches.
This gate exists because of a real failure in an earlier build of this generator: roughly 12% of pairs had the right command shape but the wrong concrete noun — a different filename than the request asked for. Neither bash -n nor ShellCheck can detect that, because a wrong filename still parses and still lints clean. argcheck.py closes exactly that gap, and it is the most important quality lesson in this dataset family.
Not validated by execution
Unlike II and III, this version was never executed at scale on real Linux. Many commands (systemctl, journalctl, apt, vmstat) are idiomatic but were validated statically only. The whole-set execution pass applied to II found 43 commands that were malformed in ways static analysis missed; comparable defects are likely present here and unremoved.
Reproduce the audit yourself
# argument-fidelity audit over the shipped file
python argcheck.py --data bash_dataset.jsonl
# re-run bash -n over the whole file
python generate.py validate-allThe standalone validate.py — including the sandboxed execution modes — ships with II and III and can be pointed at this file.
Provenance
How it is built
Generated by a recipe engine (generate.py, included and reproducible). Each recipe family emits (request, command) pairs by combining hand-written phrasing templates (imperative / question / casual) with realistic parameter pools: plausible filenames, directories, ports, services, users, patterns.
Every parameter is drawn once per example and reused in both the request and the command, so the two can never disagree.
The generator enforces:
- category quotas (40 / 35 / 25),
- a 4% per-utility cap (2,200 rows) so no command dominates — a real failure mode of earlier runs,
- minimum coverage floors for the system/info utilities above,
- SHA-1 deduplication of
(request, command)pairs, - an argument-fidelity gate (
argcheck.py) — the command must reference the same concrete nouns the request names, or the pair is rejected, - phrasing diversification so no single opening template dominates,
- a
bash -nsyntax gate on every command before it is written.
Regenerating from source
python generate.py plan # quota table + registered recipes
python generate.py run # generate to ./bash_dataset.jsonl (resumable)
python generate.py status # where a run stands
python generate.py validate-all # re-run bash -n over the whole filebash_dataset.jsonl is ground truth and a derived progress.json snapshot is rewritten after every batch, so an interrupted run continues where it stopped.
Files
generate.py and argcheck.py are pure Python 3 with no third-party dependencies. Loading the dataset requires only datasets (or nothing at all — it is plain JSONL).
Combining versions
Overlap of (request, command) pairs, measured across the shipped files:
This is version I's main remaining use. Because it overlaps III by only ~29%, deduplicating the two together yields roughly 93.5k unique pairs — meaningfully more volume than either alone. Weight it below III, which is measurably higher quality.
from datasets import load_dataset, concatenate_datasets
a = load_dataset("Frost2o24/bash-instruct-III-55k", split="train")
b = load_dataset("Frost2o24/bash-instruct-55k", split="train")
b = b.add_column("variant_group", [f"v1-{i}" for i in range(len(b))])
merged, seen = concatenate_datasets([a, b]), set()
merged = merged.filter(
lambda r: not ((k := (r["messages"][1]["content"], r["messages"][2]["content"])) in seen
or seen.add(k))
)
print(len(merged)) # ~93.5k unique pairsIntended uses & limitations
Intended uses
Teaching a small model to map natural-language requests to correct single Bash commands, short pipelines, and small scripts — including the system/info utilities listed above. In this dataset family its best role is as supplementary volume deduplicated alongside III, rather than as a primary training set.
Limitations
Please read these before reporting results.
- Superseded. III is better on every measured axis. Use I for extra volume, not as your primary corpus.
- Not executed. Static validation only; comparable to the 43 malformed commands that whole-set execution later found in II. Some are likely still present here.
- Synthetic. Variety comes from recombining templates and token pools, not from human authorship. Phrasing is diversified — 98.7% of request strings are unique and no 4-word opening exceeds 1.4% of rows — but the underlying request shapes come from a finite set of families, and ~24% of commands recur under different phrasings.
- Valid ≠ semantically correct.
bash -n, ShellCheck, and the argument gate prove that commands parse, lint, and use the nouns the request named. None of them prove the command's logic satisfies the intent. Some pairs are plausible-but-approximate — anawkcolumn index, for instance, assumes a particular log layout. There is no human correctness audit. - Single-turn, one canonical answer. No explanations, alternatives, negative examples, reasoning traces, or multi-turn dialogue.
- GNU/Linux-centric. Commands assume GNU coreutils and a systemd-based distribution. BSD/macOS flag differences (
sed -i,stat,ps) are not covered, and neither arezsh/fish. - Short scripts only. Mean 9.8 lines, no function definitions, minimal
trapand here-doc coverage. Not a source for large structured shell programs. - Not safety-tuned. The corpus contains destructive-capable commands (
rm,chmod,chown,kill,dd) as legitimate answers to requests that ask for them. A model trained on it will emit such commands readily. Add refusal/confirmation behaviour separately if your deployment needs it, and never auto-execute model output.
Citation
If you use this dataset, please cite it:
@misc{pimplapure2026bashinstruct1,
title = {Bash Instruct: A Synthetic Instruction-Tuning Dataset for
Natural Language to Bash Generation},
author = {Pimplapure, Yash},
year = {2026},
publisher = {Hugging Face},
howpublished = {\url{https://huggingface.co/datasets/Frost2o24/bash-instruct-55k}},
note = {55,000 chat-formatted examples; 100\% \texttt{bash -n} valid,
97.9\% ShellCheck-clean. Superseded by Bash Instruct III}
}License
MIT.
Supporting this work
This dataset — generation, whole-set Linux execution, and the fine-tuning runs used to check that it actually teaches the task — was built and validated on modest consumer hardware, which is the main thing limiting how far the next version can go.
If your team has an NVIDIA DGX Spark or an AMD Ryzen AI Max+ ("Strix Halo") AI dev kit to spare, it would go directly into larger validated corpora, real fine-tuned baselines, and published eval numbers for this dataset family. Reach out via the dataset discussions tab. No obligation either way — the data stays MIT and freely available regardless.
