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)) {
zsfree(ttystrname);
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.