Skip to content
shellmap

expectexpect TCL scripting for interactive programs — spawn, expect, send, interact. pwsh has no native analogue; Pexpect / WSL fallbacks across all 5 shells

Equivalents in every shell

Bashunix
expect -c 'spawn ssh user@host; expect "password:"; send "secret\r"; interact'

Tcl-based DSL. Three primary commands: `spawn <program>` (launch the program as a child pty), `expect <pattern>` (block until pattern matches the child's output — defaults are GLOB-style; `-re` switches to regex), `send <text>` (write to the child's stdin — `\r` is the return key). `interact` hands control back to the user (the keyword scripts often use to "log in then let me type"). Inline scripts: `expect -c '<script>'`. File scripts: `#!/usr/bin/expect -f`. Install: `apt install expect`, `dnf install expect`, `pacman -S expect`, `brew install expect`. Powerful for SSH key setup, FTP automation, ssh-copy-id, su / sudo password prompts that BatchMode can't avoid.

Zshunix
expect -c 'spawn ssh user@host; expect "password:"; send "secret\r"; interact'
Fishunix
expect -c 'spawn ssh user@host; expect "password:"; send "secret\r"; interact'

Same `expect` binary. CAVEAT: fish's quoting rules differ — for one-shot inline scripts, prefer a separate `.exp` file: `echo 'spawn ssh user@host\nexpect "password:"\nsend "secret\r"\ninteract' > script.exp; expect script.exp`. Or use fish heredoc-like form: `expect -c (printf '%s\n' 'spawn ssh user@host' 'expect "password:"' 'send "secret\r"' 'interact')`. The fish-vs-bash quote-rule conflict is the most common source of "expect script that works in bash but breaks under fish".

PowerShellwindows
# pwsh has no native expect. Use WSL or install Cygwin: wsl expect -c "spawn ssh user@host; ..."

No pwsh-native expect equivalent. Closest analogues: (1) `Start-Process -RedirectStandardInput` + `-RedirectStandardOutput` for one-shot pipe-fed scenarios (no pattern-match-and-respond capability). (2) `[System.Diagnostics.Process]` with `BeginOutputReadLine` + event handlers — can WATCH child output but writing to child stdin while reading output requires careful threading. (3) The `Pexpect` Python module (`pip install pexpect`) — Pythonic API mirroring expect; run via `python -c "import pexpect; ..."` from pwsh. (4) WSL: `wsl expect -c "..."` from pwsh. For SSH-specific automation, prefer ssh keys + `BatchMode=yes` over scripting password prompts — modern, simpler, secure.

cmd.exewindows
wsl expect -c "spawn ssh user@host; expect password:; send secret\r; interact"

No cmd-native expect. WSL is the friction-free path: `wsl expect -c "..."` runs Linux expect from cmd. Legacy alternative: install Cygwin (provides expect.exe via `apt-cyg install expect`) or MSYS2 (`pacman -S expect`). For pure-cmd scripted automation of interactive programs, you're mostly stuck — Windows lacks a pty abstraction; ConPTY (Windows 10 1809+) is the low-level primitive but no high-level scripting tool wraps it cmd-style. Modern Windows answer: PowerShell's SSH client + key auth.

Worked examples

Drive ssh with a password (when keys aren't available)

Bash
expect -c 'spawn ssh user@host; expect "password:"; send "secret\r"; interact'
Zsh
expect -c 'spawn ssh user@host; expect "password:"; send "secret\r"; interact'
PowerShell
wsl expect -c 'spawn ssh user@host; expect "password:"; send "secret\r"; interact'
cmd.exe
wsl expect -c "spawn ssh user@host; expect password:; send secret\r; interact"

Automate ssh-copy-id with passphrase prompt

Bash
expect -c 'spawn ssh-copy-id user@host; expect "password:"; send "pw\r"; expect eof'
Zsh
expect -c 'spawn ssh-copy-id user@host; expect "password:"; send "pw\r"; expect eof'
PowerShell
wsl expect -c 'spawn ssh-copy-id user@host; expect "password:"; send "pw\r"; expect eof'
cmd.exe
wsl expect -c "spawn ssh-copy-id user@host; expect password:; send pw\r; expect eof"

Use a `.exp` script file for a complex flow

Bash
expect -f setup.exp
Zsh
expect -f setup.exp
PowerShell
wsl expect -f setup.exp
cmd.exe
wsl expect -f setup.exp

Pattern with regex matching

Bash
expect -c 'spawn ftp host; expect -re "Name.*: $"; send "user\r"; expect "Password:"; send "pw\r"; interact'
Zsh
expect -c 'spawn ftp host; expect -re "Name.*: $"; send "user\r"; expect "Password:"; send "pw\r"; interact'
PowerShell
wsl expect -c 'spawn ftp host; expect -re "Name.*: $"; send "user\r"; expect "Password:"; send "pw\r"; interact'
cmd.exe
wsl expect -f ftp.exp

Gotchas

  • **`spawn` runs the child under a pty, NOT a pipe — behaves differently from plain shell redirection.** Most interactive programs (ssh, ftp, passwd, su, sudo) check whether stdin is a tty via `isatty(0)`. When stdin is a pipe, they behave non-interactively (or refuse). expect's `spawn` allocates a pseudo-tty, so the child program SEES a tty and stays in interactive mode — which is exactly what you want for automation. The pty abstraction is what makes expect possible. Without expect (or pexpect, or `script(1)`), there's no portable way in shell to feed input to a tty-demanding program; you cannot just do `echo "password" | ssh user@host` — ssh refuses input from a pipe.
  • **`\r` vs `\n` — the return key is CR, not LF.** `send "text\n"` sends a LINE FEED, which interactive programs often do NOT recognize as "submit". `send "text\r"` sends a CARRIAGE RETURN — that's what your keyboard sends when you press Enter. ALWAYS use `\r` for "submit", `\n` only when the program literally wants a literal newline (rare). The CR/LF distinction trips up >50% of new expect users. If your script "sends the password but the program doesn't accept it" — try replacing `\n` with `\r`. Same trap exists in Python pexpect (`.sendline(text)` adds `\r` by default).
  • **Glob vs regex matching in `expect` patterns.** Default `expect "Password:"` matches a LITERAL substring "Password:" anywhere in the child's output buffer (glob-style — `*` and `?` are wildcards but not regex). For regex: `expect -re "Password.*: $"` (anchored at end with `$`). `-exact` forces literal match (no glob wildcards). The buffer is searched RIGHT-TO-LEFT and the LATEST match wins — useful when prompts repeat (e.g. login fails → password prompt re-appears; you want the LATEST one). For long-running prompts that may not appear immediately, set `set timeout <seconds>` (default 10s, `-1` is wait-forever). The timeout pattern: `expect { "Password:" { send "pw\r" } timeout { puts "timed out"; exit 1 } }`.
  • **Storing secrets in expect scripts is dangerous — use stdin or env.** Hardcoding `send "real-password\r"` in a `.exp` file leaves the password in plain text on disk + visible in `ps` output via `cat .../setup.exp`. SAFER patterns: (1) Read from stdin: `expect -c 'stty -echo; expect_user -re "(.*)\n"; send_user "\n"; stty echo; set pw $expect_out(1,string); ...'`. (2) Read from env: `set pw $env(MY_PASSWORD); send "$pw\r"` — script becomes `MY_PASSWORD=secret expect setup.exp`. (3) Use ssh keys + `BatchMode=yes` — the right answer for ssh-specifically, removes the need for expect entirely. expect-with-passwords is a tool for legacy systems (Cisco IOS, old IBM mainframe terminals); for modern Linux/Unix, keys are the answer.
  • **`pexpect` (Python port) is more maintainable for complex flows.** Tcl/expect syntax is terse but ages poorly — branching, error handling, JSON I/O get awkward. `pexpect` (`pip install pexpect`) wraps the same pty/spawn primitives in Python: `import pexpect; child = pexpect.spawn("ssh user@host"); child.expect("password:"); child.sendline("pw"); child.interact()`. Cross-platform: works on Linux/macOS/WSL. Windows native: `pip install pexpect winpexpect` or `pexpect.popen_spawn` (no pty — pipe-based; limited). For new automation, default to pexpect over expect; for legacy maintenance, learn enough Tcl/expect to read existing scripts.

WSL & PowerShell Core notes

pwshpwsh has no native expect. Closest pwsh-only pattern: `Start-Process -RedirectStandardInput` for one-shot input feed. For full expect semantics on Windows: WSL (`wsl expect -c ...`), Cygwin/MSYS2 (provides `expect.exe`), or `pexpect` via Python (`pip install pexpect`). Modern Microsoft's answer for SSH automation: ssh keys + `BatchMode=yes` — removes the need for password automation.
WSLWSL Linux `expect` works fully — including spawning Linux programs. Cross-boundary: `wsl expect spawn ssh.exe user@host` can launch a Windows-side ssh.exe under WSL's expect, but the pty semantics get tricky (ConPTY vs Linux pty differ). Cleanest: keep both `expect` and the spawned program on the same side of the WSL boundary.

Related commands