根据 bash 的联机帮助页,“重定向运算符可以出现在简单命令之前或出现在命令中的任何位置,也可以跟随在命令之后。”
另外,根据手册页,“一个简单的命令是一系列可选变量赋值,后跟空格分隔的单词和重定向,并由控制运算符终止。”
现在问题来了,shell所说的简单命令和命令到底是什么意思呢?因为,/bin/echo foo {var}> somefile
导致 var 没有被分配 fd。与此相反,echo foo {var}> somefile
确实会导致 fd 被分配给 var。看起来,与命令不同,它与内置命令一起使用。我注意到这种结构可以出现在使用内置命令的任何地方:{var}> someFile echo foo
works。在这种情况下,如果一个简单的命令由内置命令组成,那么一个单词或一个标记也是内置命令吗?因为手册页上是这么说的:“单词:被 shell 视为单个单元的字符序列。也称为标记。”
我还观察到,当与内置命令而不是外部命令一起使用时, fd 会被分配。那么这里有什么问题呢?我的观察正确吗?简单命令和命令到底有什么区别。联机帮助页对此非常模糊。
答案1
从我的头脑中,我猜这是因为在运行外部命令时,shell 1)分叉,2)处理子进程中的重定向,3)exec 是实际命令。通过在 2 中完成的分配var
,在子进程中,启动的程序退出后,它对父 shell 是不可见的。使用内置命令,没有 fork,并且 shell 在主 shell 进程中根据需要处理 fd,并且变量赋值在那里生效。
这并不重要,因为无论如何重定向some external command {var}>/whatever
都是无用的。外部命令无法知道为其打开了什么 fd,虽然它可以确定它有哪些 fd,但除了为这一行上的重定向而打开的 fd 之外,可能还有其他 fd,因此它无法可靠地使用该 fd 来输出到/whatever
。相反,您通常会使用固定的 fd 编号,或者使用一些命令行参数或环境变量来告诉要使用的 fd 编号。
但是您也不能在这里这样做,因为在处理命令行上的扩展时尚未设置变量,因此很难将其传递给启动的程序。unset var; /bin/echo "var=$var" {var}>/dev/null
输出只是var=
,所以也是unset var; var=$var /usr/bin/env {var}>/dev/null |grep ^var
。 (尽管在 ksh 和 zsh 中,后者似乎通过环境传递实际数字。)
这种重定向似乎唯一有意义的地方是exec {var}>/whatever
,并且作为内置变量,该变量在主 shell 中设置,并且该值可供以下命令使用。
答案2
GNU bash 文档第 3.6 节第 2 段说“每次重定向......”。
无论 fd 是否实际在诸如 之类的命令中使用cmd {fd}>file
,任何内置命令似乎都设置了 fd,而任何外部命令似乎都没有设置 fd。 bash 或指南是错误的。
Paul--) echo "Hello, Worms" {var}>real 1>&${var}
Paul--) declare -p var; ls -l real; cat real
declare -- var="11"
-rw-r--r-- 1 paul paul 13 Sep 2 10:27 real
Hello, Worms
Paul--)
Paul--) /bin/echo "So long, suckers" {why}>deal 1>&${why}
Paul--) declare -p why; ls -l deal; cat deal
bash: declare: why: not found
-rw-r--r-- 1 paul paul 17 Sep 2 10:29 deal
So long, suckers
Paul--)
事实上,$why 的值在第二个重定向(stdout)中可用,但不是指南中指定的那样:“如果提供了 {varname},则重定向将持续超出了命令的范围...”。
这种分配的唯一好处似乎是它通过名称分配一个空闲的 fd,而不是编码器必须跟踪数字:
: 9>myFirstFile
cmd >&9
: {fdLog}>myLogFile
cmd >&${fdLog}