16

I understand file descriptor (or file handler) is an file IO technique in Linux systems.

I also know that each process has 3 standard streams (namely stdin, stdout and stderr) that are represented by files with descriptors from 0 to 3.

However, I notice that all the processes I examined with lsof -p <pid> has an extra file descriptor 255 with read permission.

From this answer, I learned that this feature is specific to Bash shell, however both the answer the and the referenced source didn't really explain what this file descriptor is for.

My question:

  1. What is the 255 file descriptor for?
  2. Can I make use of it in my Bash script or is it just an internal working mechanism that is not supposed to be used/manipulated manually?
9
  • In my opinion your questions were answered on the linked page.
    – Cyrus
    Commented Oct 14, 2018 at 10:47
  • I will examine the answer again to see if it answers my question. I only noticed now that you are the one who gave that answer ;)
    – Tran Triet
    Commented Oct 14, 2018 at 10:52
  • 3
    @Cyrus saying that "it's a little trick" without explaining what that "little trick" is is not a proper answer.
    – user313992
    Commented Oct 14, 2018 at 10:52
  • The first comment on the linked answer seems to have a better discussion... The last reply is probably what you are looking for... Commented Oct 14, 2018 at 12:15
  • 2
    The standard descriptors are 0 to 2. 0 to 3 would be 4 descriptors.
    – Barmar
    Commented Oct 14, 2018 at 19:35

2 Answers 2

20

For the last part of your question:

can I use it?

From man bash:

Redirections using file descriptors greater than 9 should be used with care, as they may conflict with file descriptors the shell uses internally.

So, if you mean use as creating a new fd with that number the answer is no.

If you mean use as: "write to that fd":

$ echo hello >/dev/fd/255"

Or to read from it:

$ read a </dev/fd/255
abc
$ echo "$a"
abc

the answer is yes.
But, probably, it should be better (independent of shell) to use /dev/tty to access the tty.

what is file descriptor 255 for?

As an alternative connection to the tty in case fd 1 (/dev/stdout) and fd 0 (/dev/stdin) get blocked.

More detail.

Other shells may use a different number (like 10 in zsh)

$ zsh
mail% ls -l /proc/self/fd /proc/$$/fd/* &
[1] 3345
mail% lrwx------ 1 isaac isaac 64 Oct 14 09:46 /proc/3250/fd/0 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 /proc/3250/fd/1 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 /proc/3250/fd/10 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 /proc/3250/fd/2 -> /dev/pts/2

/proc/self/fd:
total 0
lrwx------ 1 isaac isaac 64 Oct 14 09:50 0 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 1 -> /dev/pts/2
lrwx------ 1 isaac isaac 64 Oct 14 09:50 2 -> /dev/pts/2
lr-x------ 1 isaac isaac 64 Oct 14 09:50 3 -> /proc/3345/fd

[1]  + done       ls -l /proc/self/fd /proc/$$/fd/*
mail% 

From mail list:

Fd 255 is used internally as a connection to the tty, so that it doesn't interfere with the use of exec to relocate fds. Bash also allocates high fds when handling a process substitution `<(foo)', for the same reason.
Andreas Schwab

2
  • 2
    fd 255 is not used to "keep a copy of fd 1 and fd 0" -- you can easily verify that with dd bs=1 | bash -i -c 'sleep .1; ls -l /proc/$$/fd' 2>/tmp/err | tee /tmp/out. Also, that comment from the mailing list is about when bash is run as bash scriptfile (255 being in that case the open handle to scriptfile -- and in that case, ls -l /proc/pid/fd will print very convincingly 255 -> scriptfile ;-)), not about when it's run interactively.
    – user313992
    Commented Oct 16, 2018 at 7:40
  • 1
    I'm sorry that the source code snippets and analysis from my answer didn't convince you, but just to be clear: a) it's not an "alternative" connection to the tty, but the main connection to the tty, used for all tty related purposes b) it's copied from fd 2 (stderr) or opened directly from /dev/tty, not from fd 0 or fd 1 c) if fds 0, 1 or 2 get "blocked", bash will not use that 255 fd as an alternative for reading input from the user or for writing command output, prompts, error messages, etc.
    – user313992
    Commented Oct 17, 2018 at 17:44
16

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

  1. 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()

  2. get and set the termios(3) params in jobs.c:get_tty_state() and jobs.c:set_tty_state()

  3. 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.

You must log in to answer this question.

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