Shell Terminal – How to Get Real Name of Controlling Terminal

controlling-terminalshellterminaltty

How can one get the real name of the controlling terminal (if there is one, else an error) as a pathname?

By "real name", I mean not /dev/tty, which cannot be used by other arbitrary processes to refer to the same terminal. I prefer the answer as a simple shell code (like the example below) if possible, otherwise as a C function.

Note that this must work even if the standard input is redirected, so that the tty utility cannot be used: one would get a not a tty error in such a case, since tty just prints the file name of the terminal connected to standard input.

Under Linux, one can use:

echo "/dev/`ps -p $$ -o tty | tail -n 1`"

but this is not portable, as according to POSIX, the format of the terminal name is unspecified.

Concerning C functions, ctermid (NULL) returns /dev/tty, which is useless here.

Note: according to the zsh documentation, one should be able to do

zsh -c 'echo $TTY'

but this currently (version 5.0.7) fails when both the standard input and the standard output are redirected:

$ zsh -c 'echo $TTY > /dev/tty' < /dev/null
/dev/pts/9
$ zsh -c 'echo $TTY > /dev/tty' < /dev/null > /dev/null
/dev/tty

Best Answer

The "controlling terminal" aka. ctty, is distincted from "the terminal a process is interacting with".

Standard way of getting the path of ctty is ctermid(3). Upon calling this, In freebsd since release 10, an actual path is looked up[1], while older freebsd and glibc implementations[2] unconditionally returns "/dev/tty"].

ps(1) from the linux procps 3.2.8 package, read the numerical entry in /proc/*/stat[3], and then deduct the pathname partially by guessing[4, 5] due to lack of system support[6].

However if we are not strictly interested in the ctty but any terminal associated with stdio, tty(1) prints the terminal path connected to stdin, which is identical to ttyname(fileno(stdin)) in c, and an alternative is readlink /proc/self/fd/0.


Less important thought regarding the unconditional "/dev/tty" behavior: Specs merely say the string returned by ctermid "when used as a path name, refer to the current controlling terminal", instead of some straightforward "is the path name of the current controlling terminal". It might be interpreted as that "/dev/tty" is not the controlling terminal, but only refer to the controlling terminal if the same process open(3) it. Thus not violating the "a terminal may be ctty for at most one session" rule[7].

Another consequence is that when I am without any controlling terminal, ctermid does not fail -- such failing is allowed by specs[8] --, so only can I become aware of my ctty'lessness until failing a subsequent open(3), which is okay since specs also say calling open(3) on it is not guarranteed to succeed.