在代码“ {exec> / dev / null; }> / dev / null”是什么意思?


15

当您重定向包含exec重定向的命令列表时,此后exec> / dev / null似乎仍然没有被应用,例如:

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

打印“ Hi”。

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

现在,如果将其更改为:

{ 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

真奇怪; 我无法复制close(10)。您还可以发布您跟踪的整个脚本内容吗?
DepressedDaniel

@DepressedDaniel这是完整的脚本和strace:脚本 strace
Joey Pabalinas

你有一个流浪;之后},改变的意义> /dev/null不适用于化合物列表{}毕竟。
DepressedDaniel

@DepressedDaniel啊,你是完全正确的!现在的输出是我期望的;谢谢您的回答!
乔伊·帕巴利纳斯

Answers:


17

让我们跟随

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

一步步。

  1. 有两个命令:

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

    b。 echo "Hi"

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

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

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

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

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

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


为什么在2b之前执行2a背后有什么原因?(从右到左)
Joey Pabalinas 16/12/21

5
重定向必须在对其应用命令之前执行,不是吗?否则他们将如何工作?
AlexP

啊哈,没想到那样!前两个是很好的答案。在我决定一个之前先给它一点点,但是我很感谢两种解释!
乔伊·帕巴利纳斯

不幸的是,我只能选择一个答案,所以我选择了这个答案,因为它的技术性稍差一些,因此我认为它甚至可以帮助那些不那么精通技术的用户。但是,@ DepressedDaniel在这里也有同样出色的答案它提供了更深入的解释。
乔伊·帕巴利纳斯

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

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


由于FD 3在137行上覆盖了FD 1,为什么行141不能将10指向/ dev / null?
乔伊·帕巴利纳斯

@JoeyPabalinas第141行将FD 1(即stdout)复制到10之后的下一个可用描述符中,该描述符原来是11,如您在该系统调用的返回值中所见。10只是硬编码到bash中,因此bash的描述符保存不会干扰您可以通过在脚本中操作的个位数描述符exec
DepressedDaniel

因此,无论FD 1当前指向何处,fcntl(1,F_DUPFD,10)将始终引用STDOUT。
Joey Pabalinas's

@JoeyPabalinas不确定您的问题是什么。FD 1 IS STDOUT。他们是一样的东西。
DepressedDaniel

在我的原始帖子中添加了完整的strace输出。
Joey Pabalinas's

8

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

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


+1用于以简单的方式说明差异。AlexP的答案缺少这种解释。
卡米尔Maciorowski '16
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.