奇怪的重定向行为允许输出截断

时间:2019-04-08 02:12:29

标签: c file unix

我们有一个构建脚本,可以为多种电路板类型构建嵌入式系统,并且以一种方式捕获输出时,它似乎可以正常工作。另一种方法是,它部分通过输出,然后在继续操作之前将文件截断。

工作方式:

time ( cd ~ ; builder.sh 2>&1 | tee ~/builder.out )

以及截断方式:

time ( cd ~ ; builder.sh > ~/builder.out 2>&1 )

截断似乎发生在非常特定的位置,截断后文件中的第一行始终是DEFAULT_INCDIRS=...中的qmake。它是在过程中的某个特定时刻而不是在文件达到特定大小时,这一事实似乎表明它不是某个外部文件检查器在执行截断。

无论如何,如果脚本被删除,脚本将继续写入已删除文件的索引节点,直到关闭为止,然后将其删除。

实际发生的情况是该文件似乎被截断,然后从头开始继续写入。但是我知道,如果没有程序实际上无法访问文件句柄本身,就无法做到这一点。

在上述两种情况下,构建器脚本实际上并不了解其输出文件,它只是将输出和错误消息都写入stdout中,并让shell重定向来处理它

所以我的问题是:在文件I / O的UNIX模型中有没有办法做到这一点(例如,从C文件API调用)?换句话说,当通过重定向设置了要写入的文件时,是否可以截断该文件?为什么tee变体有效?是什么阻止它被截断?

2 个答案:

答案 0 :(得分:3)

是的,正如您已经注意到的,显然有人在lseek()上呼叫ftruncate()stdout

要追踪罪犯,strace -f当然会有所帮助。当做这样奇特的事情时,您可能需要做strace -f sh -c 'build.sh 2>&1 | cat > output' > log 2>&1,因为它也会很高兴地扼杀您的strace输出。

有了日志后,找到对lseek(1,lseek(2,ftruncate(1,ftruncate(2,的调用。从那里,向后搜索到前一个exec,您应该知道。

可以合法使用stdout进行游戏的程序是cdrecord,其中至少有些版本希望将CD刻录机输出到标准输出中。

答案 1 :(得分:1)

好吧,事实证明程序可以搜索标准输出(尽管我不知道这样做的程序是否合理)。

以下程序对此进行了说明:

#include <stdio.h>

int main(void){
    for (int i = 3; i > 0; --i) {
        //rewind(stdout);
        printf("Hello, world %d !\n", i);
    }
    return 0;
}

运行此命令以捕获输出,您将得到一个包含以下内容的文件:

Hello, world 3 !
Hello, world 2 !
Hello, world 1 !

但是,如果取消注释rewind行,则只会在输出文件中以 final 行结尾。

有趣的是,由于我无法控制截断stdout的程序,因此对于“猫的无用使用”奖,这实际上可能是有用的用猫。而不是执行:

myprog >outfile 2>&1

并用myProg截断文件,我可以改为:

myprog 2>&1 | cat >outfile

并且管道将保护cat输出文件不被截断。


就实际问题而言,看来qt5basebuildroot的一部分)出于某种原因正在播放某种带有输出文件句柄的恶作剧。由于我们没有时间追赶buildroot(或创建补丁文件)来正确修复它,因此我们已经通过上面的cat方法解决了该问题。