That 255
file descriptor is an open handle to the controlling tty and is only used when bash
is run in interactive mode.
It allows you to redirect the stderr
in the main shell, while still allowing the job control to function (ie. be able to kill processes with ^C, interrupt them with ^Z, etc).
Example:
$ exec 2> >(tee /tmp/err); ls /nosuchfile; sleep 1000
If you try that in a shell like ksh93
, which is simply using file descriptor 2 as a reference to the controlling terminal, the sleep
process will become immune to ^C and ^Z, and will have to be killed from another window/session. That's because the shell will not be able to set the process group of sleep
as the foreground one in the terminal with tcsetgrp()
, since file descriptor 2 no longer points to the terminal.
This is not bash
specific, it's also used in dash
and zsh
, only that the descriptor is not moved that high (it's usually 10).
zsh
will also use that fd to echo prompts and user input, so simply the following will work:
$ exec 2>/tmp/err
$
It has nothing to do with the file handles bash
is using when reading scripts and setting up pipes (which are also dup'ed out of the way with the same function -- move_to_high_fd()
), as it was suggested in other answers and comments.
bash
is using such a large number in order to allow fds larger than 9
to be used with in-shell redirections (eg. exec 87<filename
); that's not supported in other shells.
You can use that file handle yourself, but there's little point in doing so, because you can get a handle to the very same controlling terminal in any command with ... < /dev/tty
.
Source code analysis of bash:
In bash
, the file descriptor of the controlling terminal is stored in the shell_tty
variable. If the shell is interactive, that variable is initialized (at startup or after a failed exec) in jobs.c:initialize_job_control()
by dup'ing it from stderr
(if stderr
is attached to a terminal) or by directly opening /dev/tty
, and is then dup'ed again to a higher fd with general.c:move_to_high_fd()
:
int
initialize_job_control (force)
int force;
{
...
if (interactive == 0 && force == 0)
{
...
}
else
{
shell_tty = -1;
/* If forced_interactive is set, we skip the normal check that stderr
is attached to a tty, so we need to check here. If it's not, we
need to see whether we have a controlling tty by opening /dev/tty,
since trying to use job control tty pgrp manipulations on a non-tty
is going to fail. */
if (forced_interactive && isatty (fileno (stderr)) == 0)
shell_tty = open ("/dev/tty", O_RDWR|O_NONBLOCK);
/* Get our controlling terminal. If job_control is set, or
interactive is set, then this is an interactive shell no
matter where fd 2 is directed. */
if (shell_tty == -1)
shell_tty = dup (fileno (stderr)); /* fd 2 */
if (shell_tty != -1)
shell_tty = move_to_high_fd (shell_tty, 1, -1);
...
}
If shell_tty
is not already the controlling tty, then it is made so:
/* If (and only if) we just set our process group to our pid,
thereby becoming a process group leader, and the terminal
is not in the same process group as our (new) process group,
then set the terminal's process group to our (new) process
group. If that fails, set our process group back to what it
was originally (so we can still read from the terminal) and
turn off job control. */
if (shell_pgrp != original_pgrp && shell_pgrp != terminal_pgrp)
{
if (give_terminal_to (shell_pgrp, 0) < 0)
shell_tty
is then used to
get and set the foreground process group with tc[sg]etpgrp
in jobs.c:maybe_give_terminal_to()
, jobs.c:set_job_control()
and jobs.c:give_terminal_to()
get and set the termios(3)
params in jobs.c:get_tty_state()
and jobs.c:set_tty_state()
get the terminal window size with ioctl(TIOCGWINSZ)
in lib/sh/winsize.c:get_new_window_size()
.
move_to_high_fd()
is generally used with all the temporary file descriptors used by bash
(script files, pipes, etc), thence the confusion in most comments that appear prominently in google searches.
The file descriptors used internally by bash
, including shell_tty
are all set to close-on-exec, so they won't be leaked to commands.