Skip to content
shellmap

Prompt for yes/no confirmation

Block until the user confirms a destructive action — single-keystroke Y/N, defaulting to NO, case-insensitive, with a timeout so CI never hangs.

How to prompt for yes/no confirmation in each shell

Bashunix
read -rp "Delete? [y/N] " ans; case "$ans" in [yY]*) ;; *) exit 1 ;; esac

`[y/N]` capital-N signals the default. `case` matches lowercase OR uppercase. Default-NO bias = "do nothing on Enter" — destructive actions should NEVER require typing N to abort.

Zshunix
read -q "ans?Delete? [y/N] " && echo confirmed || echo cancelled

zsh `read -q` reads ONE character, expects y/n, returns 0 for y and 1 for n. Cleaner than the bash `case` pattern. Note: zsh `-q` is different from bash `-q` (which doesn't exist).

Fishunix
read -lP "Delete? [y/N] " ans; if string match -qi y $ans; echo ok; else; echo cancelled; end

fish `string match -qi` is case-insensitive `q`uiet match. `-l` makes the var local to the function (good hygiene). Add `-n 1` to read just one character without Enter.

PowerShellwindows
$choice = $Host.UI.PromptForChoice("Confirm", "Delete?", @("&Yes", "&No"), 1); if ($choice -eq 0) { "ok" } else { "cancelled" }

`$Host.UI.PromptForChoice` is the canonical pwsh API. `&` before letter sets the accelerator (Y, N). The final `1` is the default-choice INDEX (0=Yes, 1=No) — default-NO is the safe pick for destructive actions.

cmd.exewindows
choice /c yn /d n /m "Delete?"

`/c yn` = allowed keys. `/d n` = default (10s timeout, then default fires). `errorlevel` after: 1 for `y`, 2 for `n`. Inverted from convention — check accordingly: `if errorlevel 2 (echo cancelled) else (echo ok)`.

Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.

Gotchas & notes

  • **ALWAYS default to NO** for destructive actions. Capital-N in `[y/N]` is the universally-recognised "Enter = no" signal. Anti-pattern: `[Y/n]` for `rm -rf` style commands — users muscle-memory-tap Enter expecting confirmation prompts to be safe, and you've just nuked their files. apt, brew, npm, etc. all use `[y/N]` for ANY non-recoverable action; emulate that. Exception: idempotent operations (`apt update`, `git fetch`) can default-YES because there's no harm.
  • **Single-keystroke vs Enter-required**: `read -n 1` (bash) reads exactly one character without requiring Enter. Faster UX but TYPOS COMMIT — if the user fat-fingers `y` for `n`, they've confirmed. For destructive actions, REQUIRE Enter (no `-n 1`) and prefer typing the full word: `Type "DELETE" to confirm: ` checked against `[ "$ans" = "DELETE" ]`. AWS CLI does this for delete-bucket; npm does it for unpublish. Friction is the feature.
  • **Idempotent CI-safe prompts**: scripts run interactively AND in CI need a no-prompt mode. Standard pattern: `--yes`/`-y` flag (`if [[ "$1" == "-y" ]]; then ans=y; else read -rp "Delete? " ans; fi`), or `--force` for "skip every confirmation". Detect non-interactive: `[ -t 0 ]` (true if stdin is a terminal — false in CI). Combined: `if [ -t 0 ] && [ "$YES" != "1" ]; then read …; fi`. This makes the same script work in `bash deploy.sh` (interactive) and `YES=1 bash deploy.sh` (CI).
  • **pwsh `SupportsShouldProcess` is the right abstraction for cmdlets**: `function Remove-X { [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact="High")] param($Path) if ($PSCmdlet.ShouldProcess($Path, "Remove")) { … } }` — automatically respects `-WhatIf` (dry run) and `-Confirm` (force prompt). `ConfirmImpact="High"` means the prompt fires by default even without `-Confirm`. This is how `Remove-Item -Recurse` and `Stop-Computer` work. For one-off scripts the simple `$Host.UI.PromptForChoice` pattern is fine; for reusable cmdlets, ShouldProcess is the contract.
  • **Timeouts prevent zombie scripts**: `read -t 30 -p "Continue? [y/N] " ans || ans=N` (bash). Without a timeout, a script left at a prompt in a forgotten tmux pane sits forever holding open file descriptors / DB connections. 30 seconds is a reasonable default; 5 minutes for "you've been warned, double-confirm" prompts. cmd `choice /t 10 /d n` (`/t 10` = 10s timeout, `/d n` = default). pwsh: wrap the `Read-Host`/`PromptForChoice` in a runspace + `Wait-Job -Timeout`. Anti-pattern: setting an infinite timeout because "this is a one-time interactive thing" — there's always one developer who runs it under `nohup` by accident.

Related commands

Related tasks