---
title: "Smart shell copy"
description: "Add a Copy commands button to terminal blocks, which copies the commands without the prompts or the output."
url: "https://ewels.github.io/starlight-codeblocks/features/smart-shell-copy/"
markdown: "https://ewels.github.io/starlight-codeblocks/features/smart-shell-copy.md"
section: "Copy and run"
site: "starlight-codeblocks documentation"
context: "This page is from the documentation of starlight-codeblocks. A Starlight plugin that adds focus, line states, annotations, API auto-linking and 22 more features to the code blocks of a site."
index: "https://ewels.github.io/starlight-codeblocks/llms.txt"
---

# Smart shell copy

> Add a Copy commands button to terminal blocks, which copies the commands without the prompts or the output.

A terminal block in Starlight often shows a session: commands after a prompt, and the output they print. Readers who copy that block get the prompts and the output too, and the paste fails in their shell. Smart shell copy reads the prompts, and adds a **Copy commands** button to the title bar. The button copies the commands only.

````md
```sh frame="terminal"
$ uv tool install ruff
Resolved 1 package in 180ms
Installed 1 executable: ruff
$ ruff check src/ \
    --fix
Found 3 errors (3 fixed, 0 remaining).
```
````

The two lines that start with `$ ` are commands, and `--fix` continues the command above it. The other lines are output, in a muted colour. **Copy commands** copies the two commands without the prompts, with the continuation line kept. The copy button copies the whole block, as it does in every other block.

## Syntax

Smart shell copy has no directive. It applies to every block with a terminal frame that has at least one prompt line.

| Block | Terminal frame |
|---|---|
| A shell language, such as `sh`, `bash`, `zsh` or `powershell` | Yes, from Expressive Code's automatic frame |
| Any language with `frame="terminal"` | Yes |
| A shell language with `frame="code"` or `frame="none"` | No |

A prompt is text at the start of a line. The default prompts are `$ ` and `> `, each with a space after it. To use other prompts in one block, add `shellCopy.prompts="<text>"` to the fence line, once for each prompt, such as `shellCopy.prompts="% "`. The fence line is the first line of the code block, with the language. These prompts replace the defaults for the block.

A `pycon` block with a line that starts with `>>> ` is a Python session. A `python` or `py` block is a session when its first line starts with `>>> `. It gets the same button, and keeps the editor frame. See [Python sessions](#python-sessions).

## Examples

### A custom prompt

A PowerShell session uses its own prompt. This site adds `PS> ` to `shellCopy.prompts`, so **Copy commands** in the block below copies two commands:

````md
```powershell
PS> Get-ChildItem -Name
report.csv
summary.txt
PS> Get-Content summary.txt
Rows: 1204
```
````

### A session in any language

A block in any language can show a session, if it has `frame="terminal"`:

````md
```txt frame="terminal" title="Install the package"
$ python -m pip install example-client
Collecting example-client
Successfully installed example-client-2.1.0
```
````

## Behaviour

- Commands and output:
  - A line that starts with a prompt is a command. The prompt has its own colour, and a manual selection leaves it out.
  - A command line that ends with `\` continues on the next line. That line is part of the command too.
  - Every other line is output. Output lines have no syntax colours and use a muted colour.
  - A terminal block with no prompt line renders and copies as Expressive Code shows it.
- Copying:
  - The **Copy commands** button in the title bar copies each command without its prompt, one command on each line. After a copy, its label is **Copied** for a short time, and screen readers announce "Copied".
  - The copy button works as in every other block. It copies the whole block: the prompts, the commands and the output.
  - Hidden lines that are commands are part of the text that **Copy commands** copies. Hidden output lines are not.
- Without JavaScript:
  - The two buttons need JavaScript, as in Expressive Code. Without JavaScript, the **Copy commands** button is hidden, and readers can select the code by hand. The selection leaves out the prompts.

## Python sessions

A Python session in a docstring or a tutorial shows commands after `>>> ` and the values that they print. Smart shell copy reads these blocks with no configuration and no `frame="terminal"`:

````md
```py
>>> from collections import Counter
>>> counts = Counter(["ok", "ok", "failed"])
>>> for status, n in counts.most_common():
...     print(f"{status:<8} {n}")
...
ok       2
failed   1
```
````

**Copy commands** copies the three commands without the prompts, and keeps the two continuation lines. The rules follow the Python REPL:

- A line that starts with `>>> ` is a command.
- A line that starts with `... `, or a bare `...`, continues the command while its statement is open. A compound statement stays open until a bare `...` line. It starts with a keyword such as `def`, `class`, `if`, `for` or `with`, or with a `@` decorator. Any other statement stays open while it has an open bracket, a trailing `\` or an open triple-quoted string.
- After a complete statement, a line that starts with `...` is output, for example the text that `print("...")` shows.
- Every other line is output.

[API auto-linking](https://ewels.github.io/starlight-codeblocks/features/api-auto-linking/) links names in the commands of a session, and not in its output.

## Options

### `shellCopy`

Adds a Copy commands button to terminal blocks with prompts, which copies the commands only. Set it to `false` to turn the feature off.

- Type: `false | object`
- Default: On

### `shellCopy.prompts`

Line starts that mark a command. A code block can set its own on its fence line.

- Type: `string[]`
- Default: `['$ ','> ']`

The default prompts do not include `#`, because `#` also starts a comment in shell code. If your examples use a root prompt, add it:

```js title="astro.config.mjs"
codeblocks({
  shellCopy: { prompts: ['$ ', '# '] },
});
```

With `shellCopy: false`, terminal blocks have no **Copy commands** button, and prompts show as part of the code.

- [Configuration](https://ewels.github.io/starlight-codeblocks/configuration/): Learn how to set options for the whole site, or for a single code block.

## Limitations

- A prompt must be the first text on the line. The plugin does not find a prompt after spaces.
- Output lines lose their syntax colours, so output that contains code shows in one colour.
- `#` is not a default prompt. A line such as `# apt install curl` is output, unless you add `# ` to the prompts.
- In a Python session, a line that starts with `>>> ` is always a command, also if it is output that a command printed.

## Related

- [Hidden lines](https://ewels.github.io/starlight-codeblocks/features/hidden-lines/): hide set-up commands that readers need to copy but not to read.
- [Fill-in placeholders](https://ewels.github.io/starlight-codeblocks/features/fill-in-placeholders/): let readers type their own values into a command before they copy it.
