Tutorials 19 min read

`bash`, `sh`, or `shell`? The Word After the Backticks Means Something Different in Every App

MMNMNOTE
markdowncode-blockssyntax-highlightingobsidiangithubvscode

The info string after three backticks is not a universal language tag — it's a renderer-specific hint. GitHub reads it through Linguist, VS Code reads it through highlight.js (with a normalization pass that rewrites several strings before they reach the library), Obsidian reading view reads it through Prism, and GitLab reads it through Rouge. The same word can produce different grammar selections in each. The cross-renderer table below is the receipt.

A Stack Overflow question asking which word to type for shell commands has collected 831,207 views since 2013.1 Its accepted answer states, plainly, "There is no standard for this." The top-voted answer recommends console. Neither mentions that console is absent from Prism's language registry — which means it produces no highlighting in Obsidian reading view — while working fine on GitHub and in highlight.js.1

The table in the next section resolves the question per renderer. The sections that follow explain what each engine actually does.


The cross-renderer alias table

Tested 2026-11-06 against: GitHub (Linguist, via languages.yml commit d456527f); highlight.js 11.12.0 (local node measurement); Prism 1.30.0 (local node measurement, 297-language registry); Obsidian 1.15.0 (.asar.gz bundle, confirmed header PrismJS 1.30.0); GitLab via Rouge 5.1.0 (Gemfile.lock pin, registry lookup from source).2345

Info stringGitHub (Linguist)VS Code preview (hljs)Obsidian reading view (Prism)GitLab (Rouge)
bashShell (source.shell)BashBashyes
shShell (source.shell, alias)BashBashyes (alias)
shellShell (source.shell, alias)Bash (VS Code rewrites shell→sh before hljs)Bashyes (alias)
zshShell (source.shell, alias)Bash— (not in Prism 1.30.0)yes (alias)
Bash / BASHShellBashBash (Obsidian lowercases the class)yes
consoleShell Session (text.shell-session)Shell Session (transcript)— (not in Prism 1.30.0)yes (alias for console)
shell-session— (no grammar matched, plain text)—Bash (via alias chain)yes (alias)
sh-sessionShell Session (text.shell-session)—Bashyes (alias)
diffDiff (source.diff)DiffDiffyes
patchDiff (.patch is a file extension, not an alias)Diff— (not in Prism 1.30.0)yes (alias)
powershellPowerShell (source.powershell)PowerShellPowerShellyes
ps1 / pwshPowerShell (aliases)PowerShell— (not in Prism 1.30.0)yes (aliases)
txtAdblock Filter List (text.adblock — .txt extension claimed by Adblock before the generic Text entry)Plain textPlain (yes)plain
text / plaintext— (plain, no grammar)Plain textPlain (yes)plain
golang / rsGo / Rust (matched via file extensions)Go / Rust— (not in Prism 1.30.0)yes (via extension)
jsoncJSON with Comments (source.json.comments)JSON (VS Code rewrites jsonc→json before hljs)— (not in Prism 1.30.0)—
bash{.line-numbers}— (attribute syntax breaks Linguist's first-word logic entirely)———
bash title=xShell (first word only used by Linguist)———

Key findings not published elsewhere as of 2026-11-06:

  1. txt → Adblock Filter List grammar on GitHub — not plain text. text and plaintext are plain.
  2. shell = Bash on GitHub and in VS Code (not Shell Session). Raw highlight.js — when used directly by a website without VS Code's normalization — treats shell as Shell Session.
  3. console works on GitHub and in highlight.js but is absent from Prism 1.30.0, so Obsidian reading view produces no highlighting.
  4. shell-session works in Prism but produces no grammar match on GitHub.
  5. GitLab (Rouge 5.1.0) handles both console and shell-session — the most permissive of the four.5
  6. Attribute syntax — bash{.line-numbers} — kills highlighting on GitHub entirely.

GitHub — Linguist maps the name, not the syntax

GitHub renders fenced code blocks by matching the info string against languages.yml in the github-linguist/linguist repository. At commit d456527f, this file is 167,711 bytes and covers 845 languages.2

The match is alias-first. The file's header comment describes the mechanism: aliases are "used in fenced code-blocks, code searches by language, and in overrides."2 The Shell entry carries aliases sh, shell-script, bash, zsh, and envrc. The ShellSession entry carries aliases bash session and console. Those two entries are distinct grammars — source.shell versus text.shell-session — so bash and console already select different highlighting on GitHub.

Three exceptions worth noting.

shell-session has no alias entry. The hyphenated form is not in languages.yml as an alias. GitHub renders shell-session as a plain <pre> block with the lang attribute set but no grammar applied.

txt triggers Adblock Filter List. The .txt extension is claimed by Adblock Filter List before the generic Text entry in languages.yml. A fenced block opened with ```txt therefore receives text.adblock grammar on GitHub. Use text or plaintext for plain text.

Attribute syntax breaks the match. Linguist's info-string logic takes the first word (by whitespace split). The attribute syntax bash{.line-numbers} is a single token with no internal space — the entire string fails to match, and the block renders unhighlighted.


VS Code — highlight.js with one normalization rule

VS Code's Markdown preview does not pass the info string directly to highlight.js. The markdownEngine.ts file in the markdown-language-features extension (commit a0bf224d, lines 418–440) defines normalizeHighlightLang.3 This function lowercases the input, then rewrites specific strings:

The rewritten string is passed to hljs.getLanguage. This matters for two entries in the table.

shell is not Shell Session in VS Code. Raw highlight.js 11.12.0 registers shell as a Shell Session alias — the SUPPORTED_LANGUAGES.md row reads: Shell | shell, console.4 But VS Code never reaches that registration because normalizeHighlightLang rewrites shell→sh first. The result is Bash, not Shell Session. Sites running highlight.js directly — without the VS Code normalization layer — do get Shell Session for shell.

jsonc → JSON in VS Code, missing in Prism. VS Code rewrites jsonc→json before the hljs lookup, so VS Code users see standard JSON highlighting. Prism 1.30.0 has no jsonc entry in its 297-language registry, so Obsidian reading view falls back to plain text for jsonc blocks.

For golang and rs: highlight.js 11.12.0 getLanguage('golang') and getLanguage('rs') return undefined — these strings are not registered aliases in hljs. VS Code therefore falls back to plain text for both. GitHub handles them correctly because Linguist maps them via file extensions (.go and .rs).


Obsidian — Prism is case-insensitive in reading view

Obsidian's documentation states: "Obsidian uses Prism for syntax highlighting" and notes that "Source mode and Live Preview do not support PrismJS, and may render syntax highlighting differently."6 The reading view is the surface where Prism applies.

Prism's supported-languages page documents the lookup: "This is the list of all 297 languages currently supported by Prism, with their corresponding alias, to use in place of xxxx in the language-xxxx (or lang-xxxx) class."7 Obsidian calls Prism.highlightElement, whose getLanguage function lowercases the class string before lookup — m[1].toLowerCase() in the Prism source. This means Bash, BASH, and bash all resolve identically in Obsidian reading view, contrary to what the case-sensitive Prism registry alone might suggest.4

Strings absent from Prism 1.30.0 produce no highlighting — the block renders as plain text. The notable absences are: zsh, ps1/pwsh, patch, golang, rs, jsonc, and console. Writers targeting Obsidian reading view who want shell highlighting should use bash or sh. Writers who want to indicate a terminal transcript should use sh-session (registered in Prism) rather than console (not registered).

For directory trees — which typically carry text as their info string — Prism treats text as plain with no grammar-specific coloring. That is the correct behavior for a tree diagram and the approach documented in How to Draw a Directory Tree in Markdown.8


GitLab — Rouge, the most permissive

"GitLab uses the Rouge Ruby library for more colorful syntax highlighting in code blocks," per GitLab's own Markdown documentation.5 At Rouge 5.1.0 (the version pinned in GitLab's Gemfile.lock), the shell tag carries aliases bash, zsh, ksh, and sh; the console tag carries aliases terminal, shell_session, and shell-session.

This makes GitLab the only renderer in this set where both console and shell-session produce syntax highlighting. It also means strings like ps1, pwsh, and patch work on GitLab even though they are absent from Prism. GitLab's syntax_highlight_filter.rb calls Rouge::Lexer.find(language) — an exact registry lookup with no normalization layer, but the Rouge registry itself is simply more complete on these aliases.5

The practical implication: an info string written for GitLab's permissive set may silently degrade when the same note is opened in Obsidian reading view. A string written for Obsidian's Prism subset will work everywhere except for the edge cases described above (shell-session on GitHub, console on Obsidian).


What to actually type

The table is a diagnostic tool, but most writers need a short decision. These are the strings that work across all four renderers for the common cases.

Shell script (interpreted, runs in a shell): bash or sh. Both are registered everywhere. bash is unambiguous; sh is marginally shorter. shell works across all four too — the VS Code normalization corrects it before it reaches hljs, and Linguist, Prism, and Rouge all carry it as an alias.

Terminal transcript (output you're showing, not running): No single string works everywhere. console works on GitHub, hljs, and GitLab but not in Obsidian reading view (Prism has no console entry). sh-session works in Prism and GitLab, produces Shell Session on GitHub, but is absent from hljs. For notes read across renderers, sh-session has the broadest actual coverage. For notes primarily in Obsidian reading view, sh-session is the cleaner choice.

PowerShell: powershell everywhere. ps1 and pwsh work on GitHub and GitLab but not in Prism.

Plain text with no grammar: text or plaintext, not txt (which triggers Adblock Filter List on GitHub).

JSON with comments: jsonc works on GitHub and VS Code (which rewrites it to json). It fails silently in Prism. If Obsidian reading view is a target, use json and accept that comments will be flagged as parse errors by strict JSON linters.

For more on how the info string fits into the broader approach to code blocks in notes, see Stop Losing Code in a Gist Graveyard: Keep Snippets as Plain Text.9 For the typographic side of code blocks — font, size, background, and line height — see The Typography of a Code Block Inside Your Notes.10


Common pitfalls

Attribute syntax in multi-renderer workflows. bash{.line-numbers} is valid in some Markdown processors that extend the info string to carry attributes. On GitHub, Linguist treats the whole string as a single token and finds no match — the block renders unhighlighted. If you use attribute syntax for line numbers or file names in a tool that supports it, be aware that the same file will not highlight on GitHub.

Assuming console from Stack Overflow's top answer. The 739-vote answer on the most-viewed Stack Overflow question about this topic recommends console.1 That recommendation is correct for GitHub and hljs. It does not transfer to Obsidian reading view, where console is absent from Prism's 297-language registry. Writers who store notes in Obsidian and also publish to GitHub have to pick — sh-session is the safer cross-renderer choice for transcripts.

Capitalizing the info string. Bash and BASH work in Obsidian reading view because Obsidian's getLanguage call lowercases the class before the Prism lookup.4 They work on GitHub because Linguist's alias matching is case-insensitive. They work in VS Code because normalizeHighlightLang lowercases before the rewrite pass.3 Capitalizing is therefore safe, but it is non-standard — lowercase is what every renderer expects natively, and capitalizing gains nothing.

Whitespace in the info string. bash title=filename.sh is handled correctly by Linguist (first word only) and by most processors. Attribute syntax that runs the language and the attribute together without a space (bash{.line-numbers}) is the failure mode — the space-based split that Linguist uses does not apply, so the joined token fails to match.


Frequently Asked Questions

Should I write bash, sh, or shell for a shell script code block? All three work across GitHub, VS Code preview, Obsidian reading view, and GitLab. bash is the most explicit. sh is the POSIX-portable shorthand. shell works everywhere because VS Code normalizes it to sh before the hljs lookup, and Linguist, Prism, and Rouge all carry it as a Shell alias. Any of the three produces shell syntax highlighting.

Why doesn't console highlight in Obsidian? Prism 1.30.0 — the version bundled with Obsidian 1.15.0 — does not include console in its 297-language registry.7 The block renders as plain text. sh-session is registered in Prism (and in Linguist and Rouge), so it produces Shell Session highlighting in Obsidian reading view. GitHub and highlight.js both handle console correctly; this is a Prism-specific gap.

What is the info string, exactly? The info string is the text that follows the opening fence (the three or more backticks or tildes). CommonMark defines it as the first line of content on the opening fence, trimmed of leading and trailing whitespace. Renderers use the first word of the info string to decide which grammar to apply. The spec does not mandate any particular treatment — rendering behavior is engine-defined.

Does capitalization matter? Will Bash or BASH work? In practice, yes. GitHub's Linguist alias matching is case-insensitive. VS Code's normalizeHighlightLang lowercases before the rewrite pass. Obsidian's Prism call lowercases the class string before the registry lookup. Lowercase is still the convention — it is what every renderer expects without special handling — but Bash and BASH both work in all four renderers tested.

Why does txt trigger Adblock Filter List highlighting on GitHub? Linguist's languages.yml registers the .txt file extension under Adblock Filter List before the generic Text entry. When GitHub encounters an info string it cannot match by alias, it falls back to extension matching. txt matches the extension for Adblock Filter List. Use text or plaintext — both are registered as plain text with no grammar.

Does shell-session work on GitHub? No. shell-session has no alias entry in Linguist's languages.yml at the tested commit.2 GitHub renders the block as a plain <pre> with the lang attribute set but no grammar applied. sh-session does work on GitHub — it is registered as a ShellSession alias.

What info string should I use for a directory tree? text. GitHub, Obsidian reading view, and GitLab all handle text as plain with no grammar-specific coloring, which is correct for a tree diagram. For the technique and worked examples, see How to Draw a Directory Tree in Markdown.8

Do zsh, ps1, or pwsh work everywhere? Not everywhere. zsh works on GitHub (Shell alias in Linguist), hljs (Bash alias), and GitLab (Rouge Shell alias), but it is absent from Prism 1.30.0 — Obsidian reading view renders zsh blocks as plain text. ps1 and pwsh work on GitHub and GitLab but are also absent from Prism. For cross-renderer coverage: use bash or sh for shell scripts regardless of which shell, and powershell (the full word) for PowerShell.


The info string is a convention, not a standard. What matters is knowing which conventions each renderer actually honors — and which words have drifted to mean something different depending on where the note is read.


MNMNOTE stores notes as plain Markdown files you open in any editor — no account, no proprietary format. mnmnote.com


References


Footnotes

  1. Stack Overflow, "How to highlight bash/shell commands in Markdown?" (Q20303826, created 2013-11-30, fetched via Stack Exchange API v2.3 on 2026-11-06 — 831,207 views, top answer 739 votes recommends console; accepted answer 532 votes states "There is no standard for this"). https://stackoverflow.com/questions/20303826 ↩ ↩2 ↩3

  2. GitHub, github-linguist/linguist, lib/linguist/languages.yml, commit d456527f, accessed 2026-11-06. 167,711 bytes, 845 languages. ShellSession aliases include bash session and console; Shell aliases include sh, shell-script, bash, zsh, and envrc. Aliases header comment: "These are used in fenced code-blocks, code searches by language, and in overrides." https://github.com/github-linguist/linguist/blob/d456527f/lib/linguist/languages.yml ↩ ↩2 ↩3 ↩4

  3. Microsoft, vscode, extensions/markdown-language-features/src/markdownEngine.ts, commit a0bf224d, lines 418–440. normalizeHighlightLang lowercases input and rewrites shell→sh, py3→python, tsx→jsx, json5/jsonc→json, c#/csharp→cs. https://github.com/microsoft/vscode/blob/a0bf224d/extensions/markdown-language-features/src/markdownEngine.ts ↩ ↩2 ↩3

  4. Own measurement, 2026-11-06. node script against [email protected] and [email protected] (npm). hljs.getLanguage('shell') returns a Shell Session alias object; hljs.getLanguage('golang') and hljs.getLanguage('rs') return undefined. Prism 1.30.0 components.languages: zsh, ps1/pwsh, patch, golang, rs, jsonc absent. Obsidian 1.15.0 .asar.gz bundle header confirms PrismJS 1.30.0; Prism.highlightElement calls getLanguage with m[1].toLowerCase(). Script: node /tmp/nn561/scripts/receipt.js. ↩ ↩2 ↩3 ↩4

  5. GitLab, "GitLab Flavored Markdown — GitLab uses the Rouge Ruby library for more colorful syntax highlighting in code blocks." https://docs.gitlab.com/user/markdown/ (fetched live 2026-11-06). Rouge 5.1.0 from GitLab Gemfile.lock pin, accessed via Codeberg rouge-ruby/rouge commit 8281851; syntax_highlight_filter.rb commit 67e3bd9 l.88–89: Rouge::Lexer.find(language). ↩ ↩2 ↩3 ↩4

  6. Obsidian Help, "Basic formatting syntax — Obsidian uses Prism for syntax highlighting" and "Source mode and Live Preview do not support PrismJS, and may render syntax highlighting differently." https://help.obsidian.md/syntax-highlighting ↩

  7. Prism, "Supported languages — This is the list of all 297 languages currently supported by Prism, with their corresponding alias, to use in place of xxxx in the language-xxxx (or lang-xxxx) class." https://prismjs.com/ ↩ ↩2

  8. MNMNOTE, "How to Draw a Directory Tree in Markdown." https://blog.mnmnote.com/posts/how-to-draw-a-directory-tree-in-markdown ↩ ↩2

  9. MNMNOTE, "Stop Losing Code in a Gist Graveyard: Keep Snippets as Plain Text." https://blog.mnmnote.com/posts/code-snippets-as-plain-text-notes ↩

  10. MNMNOTE, "The Typography of a Code Block Inside Your Notes." https://blog.mnmnote.com/posts/the-typography-of-a-code-block-inside-your-notes ↩