tty is one of those commands that looks almost too simple to write a guide about — it prints one line and exits. But the concept behind it, the terminal device model in Linux, is something that shows up constantly once you start writing scripts, managing multiple SSH sessions, or debugging why a script behaves differently when run interactively versus from cron. So this guide covers both the command itself and the terminal device concept underneath it.
What tty Does
tty prints the filename of the terminal device connected to standard input.
tty
On an interactive SSH session, you’d typically see something like:
/dev/pts/0
If standard input isn’t connected to a terminal at all — say, it’s been redirected from a file, or the command is running inside a non-interactive script context — tty reports:
not a tty
I ran this directly inside an automated tool environment for this article, and got exactly that:
tty
not a tty
This is a genuinely useful, correct example of the exact situation where tty earns its keep: confirming programmatically whether a given process actually has an interactive terminal attached, which matters a lot for scripts that need to behave differently in interactive versus non-interactive contexts (more on that below).
Full Syntax and Options
tty [OPTION]...
-s, --silent, --quiet print nothing, only return an exit status
--help display this help and exit
--version output version information and exit
-s: Silent Mode
tty -s
echo $?
With -s, tty prints nothing at all — it just sets its exit status: 0 if standard input is a terminal, 1 if it isn’t. This is the form you actually want inside scripts, since parsing text output (“not a tty” vs a device path) is fragile compared to checking a clean exit code.
What Is a “tty,” Really? A Bit of History and Internals
The name tty itself is a holdover from “teletypewriter” — the physical hardware terminals that early Unix systems were literally connected to over serial lines. The kernel abstraction that grew out of that hardware still exists today as the terminal device concept, even though almost nobody uses an actual teletype anymore.
Every terminal device on a Linux system falls into one of a few categories:
/dev/ttyN— virtual consoles, the text-mode screens you’d reach on a physical machine withCtrl+Alt+F2and similar. Fewer people encounter these directly now that most access is remote or graphical./dev/pts/N— pseudo-terminals (“pty”), which is what almost every modern terminal session actually is: SSH sessions, terminal emulator windows (GNOME Terminal, iTerm, Windows Terminal via WSL), andtmux/screenpanes all create a/dev/pts/Ndevice./dev/console— the system console, typically used for kernel boot messages and low-level system output.
A pseudo-terminal works as a pair: a “master” side, controlled by the terminal emulator program (or SSH daemon) presenting the interface, and a “slave” side (/dev/pts/N), which is what the shell and any commands you run actually see as their controlling terminal. When you type a character, it goes into the master side, gets relayed through the pty driver in the kernel, and appears on the slave side where your shell reads it — and output flows back the same way in reverse. This is genuinely how terminal emulation and SSH sessions work under the hood, and it’s why ps can show you which pty a given shell session belongs to.
Checking Terminal Info Beyond tty
tty only tells you the device path. For more detail about the terminal’s actual capabilities and settings, related tools include:
stty -a
Shows the full terminal configuration: baud rate (mostly vestigial for virtual terminals now), line discipline settings, special characters (like the interrupt character, usually Ctrl+C), and various input/output processing flags.
echo $TERM
Shows the terminal type identifier (xterm-256color, screen, linux, etc.), which programs use to know what escape sequences and capabilities (colors, cursor movement) the terminal supports — this is looked up against the terminfo database.
ps -o tty,pid,comm
Shows which terminal each process is attached to, alongside its PID and command name — useful when trying to figure out which SSH session or terminal window “owns” a given process.
Practical Use Cases
Making a script behave differently in interactive vs non-interactive contexts:
if tty -s; then
echo "Running interactively — prompting for confirmation"
read -p "Proceed? (y/n) " answer
else
echo "Running non-interactively (cron/script) — skipping prompt"
fi
This pattern is genuinely common and important: a script that calls read for confirmation will hang forever if triggered from cron, a CI/CD pipeline, or any other non-interactive context where there’s no terminal to type a response into. Checking tty -s first lets the same script work safely in both situations.
Preventing colorized/interactive output from corrupting logs:
if tty -s; then
ls --color=auto
else
ls --color=never
fi
Many tools already auto-detect this internally (most modern CLI tools disable color automatically when output isn’t a terminal, i.e., when it’s piped or redirected), but understanding why that auto-detection works — it’s checking exactly this same condition — makes it much less mysterious when you’re debugging unexpected plain-text or unexpected color-coded output.
Identifying and messaging a specific active session:
who
alice pts/0 2026-07-31 01:10 (203.0.113.5)
bob pts/1 2026-07-31 01:22 (198.51.100.9)
write alice pts/0
The write command sends a message directly to another logged-in user’s specific terminal — the terminal device (pts/0 here) is exactly what tty would report if run in that session, tying the concept together.
Locking a script to run only from a genuine physical/local console, not remote SSH:
CURRENT_TTY=$(tty)
if [[ "$CURRENT_TTY" == /dev/tty[0-9]* ]]; then
echo "Running from local console"
else
echo "Not running from local console — refusing to proceed"
exit 1
fi
This distinguishes real virtual consoles (/dev/ttyN) from pseudo-terminals (/dev/pts/N), which is a real technique used in some security-sensitive administrative scripts that should only run from physical console access, not a remote session.
tty and Terminal Multiplexers
Tools like tmux and screen add an interesting wrinkle to how tty behaves. Each pane or window inside a multiplexer session is actually its own separate pseudo-terminal, allocated by the multiplexer itself — so running tty inside two different tmux panes in the same session returns two different /dev/pts/N paths, even though from the user’s perspective they’re both “inside the same tmux session.”
tmux new-session -d -s work
tmux split-window -h
Each resulting pane gets its own pts device, and ps -o tty,pid,comm run from either pane shows only the processes attached to that specific pane’s terminal, not the whole session. This matters when debugging a multiplexed environment — killing “the terminal” doesn’t mean what it would in a single unmultiplexed SSH session, since there are multiple real terminal devices layered underneath one multiplexer process.
This also explains a subtlety people run into with nohup and background jobs: a process detached with nohup command & and then left running after you close the terminal doesn’t lose its tty value retroactively — it keeps referencing the now-closed pseudo-terminal device until it either exits or is reparented. Checking ps -o tty,stat,comm on an orphaned background process will often show a ? in the TTY column once the original terminal is well and truly gone, signaling the process has no controlling terminal at all anymore.
Controlling Terminals and Job Control
A related but distinct concept worth understanding alongside tty is the controlling terminal — the terminal a process’s session leader is attached to, which determines where signals like SIGINT (from Ctrl+C) and SIGTSTP (from Ctrl+Z) get delivered. A process can lose its controlling terminal (for example, via setsid, or by being started as a proper daemon), at which point interactive job-control signals from a terminal no longer reach it at all — this is deliberate, standard behavior for long-running background services, which shouldn’t be interruptible by someone pressing Ctrl+C in an unrelated terminal window.
setsid command &
Running a command this way detaches it into a new session with no controlling terminal, which is part of why tty -s returning false is often used as a signal, inside daemon startup scripts, to confirm a process has correctly detached from any interactive terminal before proceeding with the rest of its startup sequence.
Troubleshooting
ttyreports “not a tty” unexpectedly during an interactive session → check whether stdin has been redirected somewhere in a wrapper script or subshell (command < /dev/null, or being called through a pipeline where stdin is the pipe, not the terminal).- A script hangs unexpectedly when run from cron → almost always a
reador other interactive prompt with notty -sguard; add one. - Output looks garbled or full of escape codes when piped to a file → the program is likely not checking
isatty()(the underlying system callttyreports on) correctly, or you’re forcing color output with a flag; check for a--color=alwaysor similar override.
Scripting Around Terminal Detection in Real Tools
A lot of everyday command-line tools quietly perform the exact same isatty() check that tty -s exposes, and knowing that helps explain behavior that otherwise looks inconsistent. git, for example, pipes its diff and log output through a pager (less by default) only when standard output is an actual terminal — pipe git log into grep or redirect it to a file, and it skips the pager entirely, printing raw output instead, because it detected there’s no terminal on the other end to interactively page through. Similarly, ls, grep, and most modern CLI tools that support --color=auto are, by default, checking this same condition rather than always emitting color codes, which is exactly why colored output “disappears” the moment you redirect a command into a file or pipe it into another command, without you ever passing an explicit flag to disable it.
Writing your own scripts to respect this same convention is generally good practice for anything meant to be both human-run and script-friendly:
if tty -s; then
PAGER_CMD="less"
else
PAGER_CMD="cat"
fi
some_long_output | $PAGER_CMD
This lets the same script page long output nicely for a human sitting at a terminal, while behaving as a clean, unbuffered pass-through when it’s part of a larger automated pipeline.
Security Implications
Terminal device files themselves have permission models worth being aware of: /dev/pts/N entries are normally owned by the logged-in user and not accessible to other unprivileged users, which prevents one user from directly reading or injecting keystrokes into another user’s terminal session on a shared multi-user system. Historically, overly permissive tty device permissions have been part of privilege-escalation and session-hijacking techniques (like injecting characters into another user’s terminal buffer), which is why modern distributions default to strict per-session ownership rather than broadly writable tty devices. There’s little direct risk from the tty command itself — it’s read-only reporting — but understanding the underlying device model helps when auditing terminal-related permission issues on a shared system.
tty vs Related Commands
| Command | Purpose |
|---|---|
tty | Print the terminal device path connected to stdin |
stty | View or change terminal line settings (baud, special characters, modes) |
who | List all logged-in users and which terminal each is on |
w | Like who, plus what each user is currently running |
ps -o tty | Show which terminal a given process is attached to |
write | Send a message to a specific user’s terminal |
Compatibility Across Distributions
tty is a standard part of GNU coreutils, present by default on every mainstream Linux distribution — Debian, Ubuntu, RHEL, Fedora, Arch, openSUSE — as well as most Unix-like systems (BSD, macOS) as part of the POSIX utility set. Behavior and flags are essentially identical everywhere; this is one of the more universally portable commands in this entire series.
Summary
tty itself is a one-line answer to a one-line question — “what terminal device am I attached to, if any?” — but that question turns out to matter a lot more than it looks, especially the silent -s form used to guard interactive prompts in scripts that might run unattended. Understanding the pseudo-terminal (pts) model underneath it also demystifies a good chunk of how SSH sessions, terminal emulators, and multiplexers like tmux actually work at the device level.
References
man 1 ttyman 4 tty(terminal device driver documentation)man 1 stty- GNU coreutils manual: https://www.gnu.org/software/coreutils/manual/coreutils.html