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
$ zsh -c 'echo $TTY > /dev/tty' < /dev/null > /dev/null
  • @mikeserv I think that the ps solution covers most systems (and who doesn't help more than ps), possibly with a bit more code to handle the identifier alone (like "04"). I was wondering if there were an even more portable solution.
    – vinc17
    Commented Feb 28, 2015 at 2:19
  • It might have to do with pre-paired sets - the old bsd-style pty pairs as well, maybe. Not all ptys are UNIX 98 types. Anyway, from man xterm: -Sccn This option allows xterm to be used as an i/o channel for an existing program... The option value is a few letters of the name of a pty to use in slave mode, plus the inherited fd number. If the option contains a “/” character, it delimites the pty name from the fd.
    – mikeserv
    Commented Feb 28, 2015 at 2:31
  • @mikeserv Note that the solution doesn't work with ps from busybox (which is used by Android, BTW), even under GNU/Linux. What do you mean by "xterm can handle that 04"?
    – vinc17
    Commented Feb 28, 2015 at 2:31
  • busybox is not POSIX-conformant. toybox, however, does very well.
    – mikeserv
    Commented Feb 28, 2015 at 2:33

2 Answers 2


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.

  • This is not more portable than the ps solution I gave in my question, since not all OS have a /proc file system. Note that ps itself uses a readlink on /proc/self/fd/2 (which works even is the standard error is redirected).
    – vinc17
    Commented Feb 28, 2015 at 2:14
  • 1
    edited. and ps readlink on /proc/*/fd/2 not to find the ctty, but to seek supplementary information in order to map numeric terminal to path, see link[4] [5]. Commented Feb 28, 2015 at 7:31
  • 1
    Excellent edit. Regarding the ctty; I can't speak for vinc17, but while you can probably always write to somewhere, there's only one file that must remain open to keep your process group alive.
    – mikeserv
    Commented Feb 28, 2015 at 9:04
  • 1
    @vinc17 - if you have any file descriptors open on your ctty then you can read them with tty. stderr is probably the best because it's spec'd to be open r/w. So tty <&2.
    – mikeserv
    Commented Feb 28, 2015 at 12:20
  • 1
    That a given terminal may be ctty for at most one session does not make glibc non-conforming for its ctermid() always returning "/dev/tty". That name refers always to the controlling terminal of the process accessing it, which varies by session. The terminal is session specific, but the name by which it is accessed does not need to be.
    – PellMel
    Commented Dec 8, 2016 at 15:00

The POSIX spec really hedges its bets where the Controlling Terminal is concerned, and which it defines thus:

  • Controlling Terminal
    • The question of which of possibly several special files referring to the terminal is meant is not addressed in POSIX.1. The pathname /dev/tty is a synonym for the controlling terminal associated with a process.

That's in the Definitions list - and that's all there is there. But in General Terminal Interface, some more is said:

  • A terminal may belong to a process as its controlling terminal. Each process of a session that has a controlling terminal has the same controlling terminal. A terminal may be the controlling terminal for at most one session. The controlling terminal for a session is allocated by the session leader in an implementation-defined manner. If a session leader has no controlling terminal, and opens a terminal device file that is not already associated with a session without using the O_NOCTTY option (see open()), it is implementation-defined whether the terminal becomes the controlling terminal of the session leader.

  • The controlling terminal is inherited by a child process during a fork() function call. A process relinquishes its controlling terminal when it creates a new session with the setsid() function; other processes remaining in the old session that had this terminal as their controlling terminal continue to have it. Upon the close of the last file descriptor in the system (whether or not it is in the current session) associated with the controlling terminal, it is unspecified whether all processes that had that terminal as their controlling terminal cease to have any controlling terminal. Whether and how a session leader can reacquire a controlling terminal after the controlling terminal has been relinquished in this fashion is unspecified. A process does not relinquish its controlling terminal simply by closing all of its file descriptors associated with the controlling terminal if other processes continue to have it open.

There's a lot there left unspecified - and honestly I think it makes sense. While the terminal is a key user interface, it's also all kinds of other things in some cases - like actual hardware, or even a kind of printer - but in a lot of cases it's practically nothing at all - like an xterm which is just an emulator. It's hard to get specific there - and I don't think it would be much in the interest of Unix anyway, because terminals do a lot more than Unix.

Anyway, POSIX is also pretty iffy on how ps should behave where the ctty is concerned.

There's the -a switch:

  • Write information for all processes associated with terminals. Implementations may omit session leaders from this list.

Great. Session leaders may be omitted. That's not very helpful.

And -t:

  • Write information for processes associated with terminals given in termlist. The application shall ensure that the termlist is a single argument in the form of a <blank> or comma-separated list. Terminal identifiers shall be given in an implementation-defined format.

...which is another let-down. But it does go on to say this about XSI systems:

  • On XSI-conformant systems, they shall be given in one of two forms: the device's filename (for example, tty04) or, if the device's filename starts with tty, just the identifier following the characters tty (for example, 04 ).

That's a little better, but is not a path. Also on XSI systems there is the -d switch:

  • Write information for all processes, except session leaders.

...which is at least clear. You can specify the -output switch as well with the tty format string, but, as you've noted, its output format is implementation-defined. Still, I think it is as good as it gets. I think that - with a lot of work - the above switches in combination with some other utilities can get you a pretty good ballpark. To be quite honest though, I don't know when/how it breaks for you - and I haven't been able to imagine a situation in which it would. But, I think probably if we add fuser and find we can verify the path.

exec 2<>/dev/null
ctty=$(sh -c 'ps -p "$$" -o tty=' <&2)
sid=$(sh -c 'ps -Ao pid= -o tty=|
      grep '"$ctty$"' | 
      grep -Fv "$(ps -do pid=)"'  <&2)
find / -type c -name "*${ctty##*/}*" \
       -exec fuser -uv {} \; 2>&1  |
grep ".*$ctty.*${sid%%"$ctty"*}"

The /dev/null stuff was just to show that it could work when none of the searching subshells had any of 0,1,2 connected to the ctty. Anyway, that prints:

/dev/pts/3:          mikeserv   3342 F.... (mikeserv)zsh

Now the above gets the full path on my machine, and I imagine it would for most people in most cases. I can also imagine it could fail. It's just rough heuristics.

This is could fail for many other reasons probably, but if you're on a system which allows the session leader to relinquish all descriptors to the ctty and yet remain the sid then as the spec allows, then this is definitely not going to help. That said, I think this can get a pretty good estimate in most cases.

Of course the easiest thing to do if you have any descriptors connected to your ctty is just...

tty <&2

...or similar.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .