在代码“{ exec >/dev/null; } >/dev/null”中到底发生了什么?

Joe*_*nas 16 command-line shell bash io-redirection file-descriptors

当您重定向包含 exec 重定向的命令列表时,之后 exec >/dev/null 似乎不再应用,例如:

{ exec >/dev/null; } >/dev/null; echo "Hi"
Run Code Online (Sandbox Code Playgroud)

打印“嗨”。

我的印象是,{}除非它是管道的一部分,否则命令列表不被视为子shell,因此exec >/dev/null在我看来,它仍应应用于当前的 shell 环境中。

现在,如果您将其更改为:

{ exec >/dev/null; } 2>/dev/null; echo "Hi"
Run Code Online (Sandbox Code Playgroud)

没有预期的输出;文件描述符 1 仍然指向 /dev/null 以供将来的命令使用。这通过重新运行显示:

{ exec >/dev/null; } >/dev/null; echo "Hi"
Run Code Online (Sandbox Code Playgroud)

这不会给出任何输出。

我尝试制作一个脚本并跟踪它,但我仍然不确定这里到底发生了什么。

在这个脚本的每一点 STDOUT 文件描述符发生了什么?

编辑:添加我的 strace 输出:

read(255, "#!/usr/bin/env bash\n{ exec 1>/de"..., 65) = 65
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
close(10)                               = 0
open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_GETFD)                       = 0
fcntl(1, F_DUPFD, 10)                   = 10
fcntl(1, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
dup2(10, 1)                             = 1
fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
close(10)                               = 0
fstat(1, {st_mode=S_IFCHR|0666, st_rdev=makedev(1, 3), ...}) = 0
ioctl(1, TCGETS, 0x7ffee027ef90)        = -1 ENOTTY (Inappropriate ioctl for device)
write(1, "hi\n", 3)                     = 3
Run Code Online (Sandbox Code Playgroud)

Ale*_*exP 18

让我们跟随

{ exec >/dev/null; } >/dev/null; echo "Hi"
Run Code Online (Sandbox Code Playgroud)

一步步。

  1. 有两个命令:

    一种。{ exec >/dev/null; } >/dev/null, 其次是

    echo "Hi"

    shell 首先执行命令 (a),然后执行命令 (b)。

  2. 执行{ exec >/dev/null; } >/dev/null收益如下:

    一种。首先,shell 执行重定向>/dev/null 并记住在命令结束时撤消它

    湾 然后,shell 执行{ exec >/dev/null; }.

    C。最后,shell 将标准输出切换回原处。(这与中的机制相同ls -lR /usr/share/fonts >~/FontList.txt——重定向仅在它们所属的命令的持续时间内进行。)

  3. 一旦第一个命令完成,shell 就会执行echo "Hi". 标准输出是第一个命令之前的任何地方。

  • 重定向_必须_在它们应用的命令之前执行,不是吗?否则他们怎么工作? (5认同)

Dep*_*iel 14

为了不使用子外壳或子进程,当复合列表的输出通过{}管道传输时>,外壳会在运行复合列表之前保存 STDOUT 描述符,并在运行后将其恢复。因此exec >,复合列表中的 不会超过旧描述符恢复为 STDOUT 的点。

我们来看看 的相关部分strace bash -c '{ exec >/dev/null; } >/dev/null; echo hi' 2>&1 | cat -n

   132  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   133  fcntl(1, F_GETFD)                       = 0
   134  fcntl(1, F_DUPFD, 10)                   = 10
   135  fcntl(1, F_GETFD)                       = 0
   136  fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
   137  dup2(3, 1)                              = 1
   138  close(3)                                = 0
   139  open("/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
   140  fcntl(1, F_GETFD)                       = 0
   141  fcntl(1, F_DUPFD, 10)                   = 11
   142  fcntl(1, F_GETFD)                       = 0
   143  fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
   144  dup2(3, 1)                              = 1
   145  close(3)                                = 0
   146  close(11)                               = 0
   147  dup2(10, 1)                             = 1
   148  fcntl(10, F_GETFD)                      = 0x1 (flags FD_CLOEXEC)
   149  close(10)                               = 0
Run Code Online (Sandbox Code Playgroud)

您可以看到,在第 134 行,描述符1( STDOUT) 被复制到至少具有索引的另一个描述符上10(就是这样F_DUPFD做的;它在复制到该描述符后返回从给定数字开始的最低可用描述符)。另请参阅第 137 行如何将open("/dev/null")(descriptor 3)的结果复制到描述符1( STDOUT) 上。最后,在线上147,旧的STDOUT保存在描述符上10被复制回描述符1( STDOUT)。净效果是将更改隔离到STDOUT在线144(对应于内部exec >/dev/null)。


Jul*_*ier 9

{ exec >/dev/null; } >/dev/null; echo "Hi"和之间的区别在于,{ exec >/dev/null; }; echo "Hi"双重重定向dup2(10, 1);在关闭 fd 10stdout之前执行,这是原始文件的副本,在运行下一个命令 ( echo) 之前。

之所以会这样,是因为外部重定向实际上覆盖了内部重定向。这就是为什么它在stdout完成后复制回原始fd的原因。