给出以下代码:
out="$(mktemp)"
rm -f "$out"
clear
printf '%s\n' 0 >"$out"
{
printf '%s\n' '1' >/dev/stdout
printf '%s\n' '2' >/dev/stdout
} >"$out"
cat -e -- "$out"
rm -f "$out"
在 Ubuntu 上输出:
2$
在 MacOS 上输出:
1$
2$
当明确附加时,它们的行为一致:
out="$(mktemp)"
rm -f "$out"
clear
printf '%s\n' 0 >"$out"
{
printf '%s\n' '1' >/dev/stdout
printf '%s\n' '2' >>/dev/stdout
} >"$out"
cat -e -- "$out"
rm -f "$out"
在 MacOS 和 Ubuntu 上输出:
1$
2$
对我来说最令人困惑的例子是这个:
out="$(mktemp)"
rm -f "$out"
clear
printf '%s\n' 0 >"$out"
exec 3>>"$out"
{
printf '%s\n' '1' >/dev/stdout
printf '%s\n' '2' >/dev/stdout
} >&3
{
printf '%s\n' '3' >/dev/stdout
printf '%s\n' '4' >/dev/stdout
} >&3
cat -e -- "$out"
rm -f "$out"
exec 3>&-
在 MacOS 上输出:
0$
1$
2$
3$
4$
在 Ubuntu 上输出:
4$
我期望在 Ubuntu 上实现这一点:
0$
2$
4$
我完全搞不清楚为什么会出现这种行为,在这个例子中,以及在我设计的所有其他例子中,都出现了这种行为,以说明这种差异。
我的问题:
- 这种差异是什么?发生了什么?这种差异是故意的吗?
- 这种差异还适用于哪些地方?其根源是什么?
- 如果这种差异是故意的,那么为什么要这样做呢?哪一个应该是正确的行为?
- 在编写跨操作系统脚本时,如何才能减轻这些差异?
- 这是
shopt -o noclobber
适当的回应吗?这是真正的必要吗noclobber
?
因此,在
人们可能实际上希望重定向
> /dev/stdout
不执行任何操作,因为它>
所做的就是重定向stdout,而将stdout重定向到stdout似乎真的是一个无操作。因此看起来应该与此相同:printf 的两个副本都继承了指向相同打开文件描述(OFD) 的 fd,因此具有共享的写入位置。这意味着第二个副本的写入从第一个副本离开的位置开始,并且输出按自然顺序写入文件。
也就是说,在大多数系统上,打开
/dev/stdout
就像调用dup()
stdout (fd 1)。但在 Linux 上情况并非如此!相反,在 Linux 上,打开
/dev/stdout
(或任何类似的操作)会找到底层文件,然后重新打开它。新的文件描述符 (fd) 指向一个新的 OFD,其读/写位置和写入模式(追加还是正常)与原始文件无关。如果使用 ,它会截断文件>
。因此,上面的 shell 打开
outputfile
大括号,获取指向写入位置为 0 的 OFD 的 fd,截断文件。然后outputfile
再次打开第一个printf
,再次在写入位置 0;然后outputfile
再次打开第二个printf
,再次在写入位置 0,截断它。第一个 printf 写入的输出丢失,第二个 printf 的输出是剩下的全部内容。和
当你这样做
相反,第二个 printf 的 OFD 以追加模式(而不是截断)打开,这意味着通过它的所有写入总是到达文件末尾。
同样地,这里:
shell
outfile
在写入位置 0 处以追加模式打开,以便执行 exec;然后outfile
通过 重新打开/dev/stdout
,在 Linux 上截断文件并在写入位置 0 处以正常非追加模式获取 OFD,然后重复此操作,然后重复,然后重复。除了最后一个 printf 之外,所有写入均会丢失。在其他操作系统上,这与没有重定向的情况相同
> /dev/stdout
,所有数据都会按顺序写入文件。如果您
1<> /dev/stdout
要求进行非截断读写打开,您将看到部分数据从一开始就被覆盖:在 Linux 上,结果
outfile
包含(在 macOS 上,这需要外部重定向也请求读写打开(即
1<> outfile
),这是相当合理的,因为如果不重新打开,就无法将只写 OFD 变成读写 OFD。)这就是矛盾之处。我认为这只是 Linux 出于某种原因一直这样做的历史偶然,现在无法改变。不过,我不确定。
正确的行为是什么?好吧,如果一个操作系统执行 X,而许多其他操作系统执行 Y,那么人们可能会说 Y 至少是一种更可接受的方式。但没有权威来决定各种操作系统可以做什么。据我所知,POSIX 只编纂了已经达成共识的内容,并没有提及
/dev/stdout
其他内容,可能是因为像这样的变化……(在所有类 Unix 中,我不知道其他系统是否也做了不同的事情。/dev/stdout
但我知道至少 BSD 是一致的,而 Linux 则不同。)这取决于您想要实现的目标(并且您没有说),但首先,不要使用
/dev/stdout
。/dev/stdout
尤其是在第一个例子中,如上所述,重定向到是多余的。如果您想写入相同的 stdout,只需删除重定向。如果您想重新打开文件,只需执行以下操作:或者,更可能的情况是,你有一个程序需要文件名作为输出(并且不支持例如
-
stdout),因此必须使用/dev/stdout
。然后,只使用它来写入管道或类似的东西。管道无法定位,因此没有写入位置,并且当所有内容都只是到达管道末尾时,追加模式与正常模式没有区别。因此:
在 Linux 中的工作方式应该与在 macOS 和其他系统中相同。
或者针对单独的猫(这里很傻,但在某些情况下可能有用):