6

The key statements in the snippet below (i.e. other than those for printing labels and blank lines, for spacing) come in pairs. In each pair, the first and second statements have the forms tty... and echo $(tty...), respectively.

echo stdin:
tty
echo $(tty)

echo

echo stdout: tty <&1 echo $(tty <&1)

echo

echo stderr: tty <&2 echo $(tty <&2)

If I source a file containing this snippet (in a zsh or bash session, for example), I get the following output1 (I added the line numbers afterwards, for reference):

 1    stdin:
 2    /dev/pts/8
 3    /dev/pts/8
 4    
 5    stdout:
 6    /dev/pts/8
 7    not a tty
 8    
 9    stderr:
10    /dev/pts/8
11    /dev/pts/8

In this output, the line generated by echo $(tty <&1) (line 7) sticks out conspicuously, for two reasons:

  1. it is the only one among the echo $(tty ...)-generated lines (namely, lines 3, 7, and 11) that differs its tty ...-generated counterpart, immediately preceding it (lines 2, 6, and 10); and
  2. it differs from the output (line 11) of the formally analogous echo $(tty <&2).

Q: How are these two discrepancies explained?


For the record, I tried to find the explanation for these apparent anomalies in the manual page for tty, but, as far as I can tell, this page does not address these questions at all2; certainly not explicitly3.

This question was motivated by some of the code this great answer to an earlier question of mine.


1Of course, the actual number after /dev/pts/ will be different if I change terminals, and will likely be different for you if you try the same thing.

2In fact, the entire DESCRIPTION section of the tty manual page available on my system consists of just one sentence: "Print the file name of the terminal connected to standard input.".

3Here I am leaving open the possibility that someone with more background knowledge than I have may be able to deduce from tty's terse manual page the behavior illustrated above.

kjo
  • 15,339
  • 25
  • 73
  • 114

1 Answers1

8

tty reports (on its stdout) the name of tty device that is opened on its standard input (its fd 0).

In:

tty <&2

Which is short for

tty 0<&2

The shell redirects fd 0 to the same resource as is open on fd 2 (does a dup2(2, 0) in a new process and then runs tty in that process. So tty will have the same resource on its fd 0 and 2 and write on fd 1 the tty that is open on both (if any).

Same happens with:

tty <&1

In:

echo "$(tty <&1)"

however, fd 1 of that command substitution goes to a pipe (and the shell reads what tty outputs at the other end), it's not the same resource as where the fd 1 of echo goes to.

If you want to know what tty is open on fd 1 and use a command substitution, you'd need something like:

{ echo "$(tty <&3)"; } 3<&1

So for the process running tty, we have:

  • 3: same as the outer 1
  • 1: a pipe
  • 2: untouched
  • 0: same as 3, so same as outer 1

So tty can write to that pipe the path of the tty device that is connected to the outer stdout.

As tty doesn't need that fd 3, you can make it a bit cleaner with:

{ echo "$(tty <&3 3<&-)"; } 3<&1

That is, close it after you've used it to duplicate it to fd 0.