在Bash中,文件描述符255的作用是什么,我可以使用它吗?


10

我了解文件描述符(或文件处理程序)是Linux系统中的文件IO技术

我也知道每个进程都有3个标准流(即stdin,stdout和stderr),这些流由描述符从0到3的文件表示。

但是,我注意到我检查过的所有进程lsof -p <pid>都有一个255具有读取许可权的额外文件描述符。

这个答案中,我了解到此功能特定于Bash shell,但是答案和所引用的源都没有真正解释此文件描述符的用途。

我的问题:

  1. 255文件描述符的作用是什么?
  2. 我可以在我的Bash脚本中使用它吗,或者它只是一个内部工作机制,不应该手动使用/操纵?

我认为您的问题在链接页面上得到了回答。
赛勒斯

我将再次检查答案,以查看它是否回答了我的问题。我现在才注意到您是给出答案的人;)
Tran Triet

2
@Cyrus说“这是一个小把戏”而没有解释“小把戏”不是什么正确的答案。
mosvy

关于链接的答案的第一个评论似乎进行了更好的讨论... 最后一个答案可能是您想要的...
RubberStamp

Answers:


12

对于问题的最后一部分:

我可以使用吗?

来自man bash

应该谨慎使用使用大于9的文件描述符的重定向,因为它们可能与shell内部使用的文件描述符冲突。

因此,如果您的意思是用该数字创建新的fd,答案是否定的。

如果您将其用作:“写入该fd”:

$ echo hello >/dev/fd/255"

或从中读取:

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

答案是肯定的。
但是,可能最好使用(独立于shell)/dev/tty访问tty

文件描述符255的作用是什么?

作为替代,如果fd 1(/dev/stdout)和fd 0(/dev/stdin)被阻塞,则与tty连接。

更多细节

其他shell可能使用不同的数字(例如zsh中的10)

$ 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在处理进程替换“ <(foo)”时也会分配高fds。
安德烈亚斯·施瓦布


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 scriptfile255在这种情况下,它是scriptfile- 的打开句柄,在这种情况下,ls -l /proc/pid/fd将非常有说服力地打印出来255 -> scriptfile;-)),而不是有关何时以交互方式运行。
mosvy

很抱歉,我的答案中的源代码片段和分析未能使您信服,但请您清楚:a)它不是与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

如果您在像这样的shell中尝试该操作ksh93,它只是使用文件描述符2作为对控制终端的引用,则该sleep进程将不受^ C和^ Z的影响,并且必须从另一个窗口/会话中删除。那是因为shell不能通过sleep将该终端的进程组设置为终端中的前台进程tcsetgrp(),因为文件描述符2不再指向终端。

这不是bash特定的,它也用在dash和中zsh,只是描述符没有被移到那么高(通常是10)。

zsh 还将使用该fd回显提示和用户输入,因此只需执行以下操作:

$ exec 2>/tmp/err
$ 

正如在其他答案和评论中所建议bash的那样,这与读取脚本和设置管道时使用的文件句柄无关(它们也用相同的功能-进行了重复处理move_to_high_fd())。

bash为了允许fds大于9外壳内重定向(例如exec 87<filename)而使用了如此大量的代码;其他Shell不支持。

您可以自己使用该文件句柄,但是这样做没有任何意义,因为您可以通过使用任何命令在同一命令终端上获得一个句柄... < /dev/tty

bash的源代码分析

在中bash,控制终端的文件描述符存储在shell_tty变量中。如果外壳是交互式的,则jobs.c:initialize_job_control()通过从stderr(如果stderr附加到终端)对其/dev/tty进行复制或直接打开来初始化该变量(在启动时或执行失败后)。与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]etpgrpjobs.c:maybe_give_terminal_to()jobs.c:set_job_control()jobs.c:give_terminal_to()

  2. 获取和设置termios(3)在PARAMS 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,因此它们不会泄漏给命令。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.