Why does zsh descriptor 10 not have same offset as 0/1/2 when it is connected to same tty? Would it matter if one were to close fd10 and immediately duplicate fd0 to it? On bash the equivalent 255 has the same offset as fd0/1/2, suggesting it's the same entry in the system file table. Why would zsh want to have a different entry in system file table then?

zsh     6980  codepoet    0u   CHR   16,2 0t1266790                 749 /dev/ttys002
zsh     6980  codepoet    1u   CHR   16,2 0t1266790                 749 /dev/ttys002
zsh     6980  codepoet    2u   CHR   16,2 0t1266790                 749 /dev/ttys002
zsh     6980  codepoet   10u   CHR   16,2    0t3349                 749 /dev/ttys002

1 Answer 1


If you run:

strace -e open,openat,fcntl,dup,dup2 zsh -f

Or the equivalent on your system, you see:

openat(AT_FDCWD, "/dev/pts/2", O_RDWR|O_NOCTTY) = 3
fcntl(3, F_DUPFD, 10)                   = 10

zsh has opened the tty anew for fd 10.

While for bash --norc, you see:

dup(2)                                  = 3
dup2(3, 255)                            = 255

bash's fd255 is just a dup of fd2.

size/offset don't make sense for a tty device, I don't know what those values are meant to represent on your system, but in any case, the fact that they are different can be explained by the fact that with zsh, fd 10 is pointing to a different (new) open file description from those on fd 0, 1 or 2 while with bash, fd 255 is pointing to the same open file description as on fd 2.

That won't make much of a difference in practice.

As to why zsh opens the tty device anew, if you look at the code¹, it's to:

    /* Make sure the tty is opened read/write. */
    if (isatty(0)) {
        if ((ttystrname = ztrdup(ttyname(0)))) {
            SHTTY = movefd(open(ttystrname, O_RDWR | O_NOCTTY));

Note that POSIX shells are meant to be interactive only when both stdin and stderr go to a terminal and are not interpreting scripts.

If the -i option is present, or if there are no operands and the shell's standard input and standard error are attached to a terminal, the shell is considered to be interactive.

zsh doesn't need stderr to go to a terminal.

zsh's fd 10 (actually first free fd above 9) is not exactly the same as bash's fd 255 (first free fd below 256). zsh's is used by its line editor for I/O and terminal interaction generally, while bash's is used for job control operations only AFAICT and readline uses stdin for input and stderr for input echo and doesn't work properly if fd0 is not opened for reading or fd2 is not opened for writing.

¹ That code changed some time after 2.5 in the mid 90s. Before that, there was instead SHTTY = movefd((isatty(0)) ? dup(0) : open("/dev/tty",O_RDWR)), suggesting you could have an interactive shell even when stdin was not a terminal, and you wouldn't have had a different open file description on that fd. Actually, using /dev/tty which points to the session's controlling terminal is more correct for job control interactions than using the tty on fd 0 or 2 like zsh and bash currently do even if in practice that's very unlikely to make a difference.

You must log in to answer this question.

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