tty Command in Linux: Complete Guide to Terminal Identification and Parameters

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:

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

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

CommandPurpose
ttyPrint the terminal device path connected to stdin
sttyView or change terminal line settings (baud, special characters, modes)
whoList all logged-in users and which terminal each is on
wLike who, plus what each user is currently running
ps -o ttyShow which terminal a given process is attached to
writeSend 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

Exit mobile version