В Bash, для чего нужен файловый дескриптор 255, могу ли я его использовать?

10

Я понимаю, что файловый дескриптор (или файловый обработчик) - это метод файлового ввода-вывода в системах Linux.

Я также знаю, что у каждого процесса есть 3 стандартных потока (а именно stdin, stdout и stderr), которые представлены файлами с дескрипторами от 0 до 3.

Однако я заметил, что у всех процессов, которые я исследовал, lsof -p <pid>есть дополнительный файловый дескриптор 255с разрешением на чтение.

Из этого ответа я узнал, что эта особенность специфична для оболочки Bash , однако и ответ, и источник, на который ссылаются, действительно не объясняли, для чего предназначен этот файловый дескриптор.

Мой вопрос:

  1. Для чего нужен файловый дескриптор 255?
  2. Могу ли я использовать его в своем скрипте Bash или это просто внутренний рабочий механизм, который не предполагается использовать / манипулировать вручную?
Чан Триет
источник
На мой взгляд, ваши вопросы были даны ответы на связанной странице.
Сайрус
Я проверю ответ еще раз, чтобы увидеть, отвечает ли он на мой вопрос. Я только сейчас заметил, что ты тот, кто дал такой ответ;)
Чан Триет,
2
@Cyrus, говоря, что «это маленькая хитрость», не объясняя, что это за «маленькая хитрость», не является правильным ответом.
мосвы
Первый комментарий к связанному ответу, кажется, лучше обсуждался ... Последний ответ , вероятно, то, что вы ищете ...
RubberStamp
Связанный: askubuntu.com/a/866722/772601
Исаак

Ответы:

12

Для последней части вашего вопроса:

я могу использовать это?

От man bash:

Перенаправления с использованием файловых дескрипторов больше 9 следует использовать с осторожностью, поскольку они могут конфликтовать с файловыми дескрипторами, которые оболочка использует внутри.

Итак, если вы имеете в виду использовать как создание нового fd с этим номером, ответ - нет.

Если вы имеете в виду использовать как: "написать в этот FD":

$ echo hello >/dev/fd/255"

Или читать из него:

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

ответ да.
Но, вероятно, это должно быть лучше (независимо от оболочки) использовать /dev/ttyдля доступа к tty.

для чего нужен файловый дескриптор 255?

Как альтернативное подключение к tty в случае fd 1 (/dev/stdout/dev/stdin блокировки ) и fd 0 ( ).

Подробнее .

Другие оболочки могут использовать другое число (например, 10 в 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% 

Из списка рассылки :

Fd 255 используется внутри как соединение с tty, чтобы не мешать использованию exec для перемещения fds. Bash также выделяет высокие fds при обработке подстановки процесса `<(foo) 'по той же причине.
Андреас Шваб

Исаак
источник
fd 255 не используется для «хранения копии fd 1 и fd 0» - вы можете легко проверить это с помощью dd bs=1 | bash -i -c 'sleep .1; ls -l /proc/$$/fd' 2>/tmp/err | tee /tmp/out. Кроме того, этот комментарий из списка рассылки касается того, когда bashон запускается как bash scriptfile( 255будучи в этом случае открытым дескриптором для scriptfile- и в этом случае ls -l /proc/pid/fdбудет печататься очень убедительно 255 -> scriptfile;-)), а не о том, когда он запускается в интерактивном режиме.
Мосви
Я сожалею, что фрагменты исходного кода и анализ из моего ответа не убедили вас, но просто для ясности: а) это не «альтернативное» соединение с tty, а основное соединение с tty, используемое для всех Связанные с tty цели b) он скопирован из fd 2 (stderr) или открыт непосредственно из /dev/tty, а не из fd 0 или fd 1 c) если fds 0, 1 или 2 заблокированы, bash не будет использовать эти 255 fd в качестве альтернативы для чтение ввода от пользователя или для записи вывода команды, подсказок, сообщений об ошибках и т. д.
mosvy
8

Этот 255файловый дескриптор является открытым дескриптором для управляющего tty и используется только при bashзапуске в интерактивном режиме.

Это позволяет вам перенаправить stderr в основную оболочку, в то же время позволяя функционировать элементам управления заданиями (т.е. иметь возможность убивать процессы с помощью ^ C, прерывать их с помощью ^ Z и т. Д.).

Пример:

$ exec 2> >(tee /tmp/err); ls /nosuchfile; sleep 1000

Если вы попробуете это в подобной оболочке ksh93, которая просто использует дескриптор файла 2 в качестве ссылки на управляющий терминал, sleepпроцесс станет невосприимчивым к ^ C и ^ Z, и его придется убить из другого окна / сеанса. Это связано с тем, что оболочка не сможет установить группу процессов sleepкак переднюю в терминале tcsetgrp(), поскольку файловый дескриптор 2 больше не указывает на терминал.

Это не является bashконкретным, оно также используется dashи zsh, только, дескриптор не перемещается так высоко (это обычно 10).

zsh также будет использовать этот fd для вывода подсказок и ввода данных пользователем, поэтому просто будет работать следующее:

$ exec 2>/tmp/err
$ 

Это не имеет ничего общего с файловыми дескрипторами bash, которые используются при чтении скриптов и настройке каналов (которые также были дублированы с помощью той же функции - move_to_high_fd()), как это было предложено в других ответах и ​​комментариях.

bashиспользует такое большое число для того, чтобы разрешить fds больше, чем 9при перенаправлениях в оболочке (например, exec 87<filename); это не поддерживается в других оболочках.

Вы можете использовать этот дескриптор файла самостоятельно, но в этом нет особого смысла, потому что вы можете получить дескриптор того же управляющего терминала в любой команде с помощью ... < /dev/tty.

Анализ исходного кода bash :

В bashфайле дескриптор управляющего терминала хранится в shell_ttyпеременной. Если оболочка является интерактивной, эта переменная инициализируется (при запуске или после неудачного выполнения) jobs.c:initialize_job_control()путем ее дублирования stderr(если stderrона подключена к терминалу) или путем непосредственного открытия /dev/tty, а затем снова дублируется на более высокий fd с 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);
      ...
    }

Если shell_ttyэто еще не управляющий tty, то это сделано так:

          /* 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 затем используется для

  1. получить и установить группу процессов переднего плана с помощью tc[sg]etpgrpin jobs.c:maybe_give_terminal_to(), jobs.c:set_job_control()иjobs.c:give_terminal_to()

  2. получить и установить termios(3)параметры в jobs.c:get_tty_state()иjobs.c:set_tty_state()

  3. получить размер окна терминала с ioctl(TIOCGWINSZ)в lib/sh/winsize.c:get_new_window_size().

move_to_high_fd() обычно используется со всеми дескрипторами временных файлов, используемых bash (файлы сценариев, каналы и т. д.), поэтому путаница в большинстве комментариев, которые заметно выделяются при поиске в Google.

Файловые дескрипторы, используемые внутренне bash, в том числе shell_tty, установлены на close-on-exec, поэтому они не будут передаваться командам.

mosvy
источник