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

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

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

{ exec >/dev/null; } >/dev/null; echo "Hi"

打印出“嗨”。

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

现在如果你将其更改为:

{ exec >/dev/null; } 2>/dev/null; echo "Hi"

没有预期的输出;对于将来的命令,文件描述符 1 仍指向 /dev/null。重新运行可以显示这一点:

{ exec >/dev/null; } >/dev/null; echo "Hi"

这不会给出任何输出。

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

在此脚本中的每一点,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

答案1

让我们跟随

{ exec >/dev/null; } >/dev/null; echo "Hi"

一步步。

  1. 有两个命令:

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

    b.echo "Hi"

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

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

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

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

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

  3. 一旦第一个命令完成,shell 就会执行echo "Hi"。标准输出位于第一个命令之前的位置。

答案2

为了不使用子 shell 或子进程,当复合列表的输出{}通过管道传输时>,shell 在运行复合列表之前保存 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

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

答案3

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

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

相关内容