Pseudo-Terminals (PTY)
Pseudo-Terminals (PTY)
Overview
A pseudo-terminal makes a program believe it is attached to a real terminal: line discipline, window size, and isatty checks. You need PTYs when automating interactive tools (ssh, sudo, password prompts, TUI apps) from a non-interactive parent.
Stdlib does not ship a full PTY API. Common approach: github.com/creack/pty (or platform x/sys + careful setup).
TTY detection (stdlib)
func isTerminal(f *os.File) bool {
fi, err := f.Stat()
if err != nil {
return false
}
return fi.Mode()&os.ModeCharDevice != 0
}CLIs use this for color, paging, and prompts (chapter 145).
Why pipes are not enough
parent --pipe--> child
Many programs disable interactive features when stdin is a pipe (Not a tty). A PTY pair fixes that:
parent <-> PTY master <-> PTY slave <-> child (thinks it's a terminal)
Start a command under a PTY (creack/pty)
go get github.com/creack/pty@latestimport (
"io"
"os"
"os/exec"
"github.com/creack/pty"
)
func runInPTY(name string, args ...string) error {
cmd := exec.Command(name, args...)
ptmx, err := pty.Start(cmd)
if err != nil {
return err
}
defer ptmx.Close()
// copy PTY <-> user terminal
go io.Copy(ptmx, os.Stdin)
_, err = io.Copy(os.Stdout, ptmx)
return err
}go run . -- bash -l # interactive shell session sketchWindow size
// propagate terminal resize (Unix)
_ = pty.InheritSize(os.Stdin, ptmx)
// listen for SIGWINCH and call InheritSize againWithout this, full-screen TUIs mis-draw after resize.
Scripted interaction
ptmx, err := pty.Start(exec.Command("mysql", "-u", "root", "-p"))
// carefully write password + "\n" after prompt detection
// prefer non-interactive flags when possibleSecurity: automating passwords is fragile and audit-hostile—prefer socket auth, env files with strict modes, or API tokens.
When to avoid PTYs
| Prefer | Avoid PTY when |
|---|---|
cmd.Stdin + flags/env |
App has real non-interactive mode |
Expect scripts (npm test) |
You only need stdout capture |
| Stdlib pipes | Cross-platform simplicity matters |
Minimal tool: runtty
runtty -- <command>
# allocates PTY, connects to current terminal
Useful for wrapping tools that refuse pipes.
Rules of thumb
| Do | Don’t |
|---|---|
| Prefer non-interactive APIs | PTY-scrape fragile prompts in CI |
Handle SIGWINCH for TUIs |
Assume 80x24 forever |
| Close master FD | Leak PTYs (FD exhaustion) |
| Document platform deps | Expect identical PTY behavior on Windows without code |
Try next
- Run
ls --color=autounder pipe vs PTY; compare color.
- Automate a simple
readshell script via PTY write.
- Wire resize handling and test with a TUI (
top,htop).