命令行程序可以防止其输出重定向吗?


49

我已经习惯了这样做: someprogram >output.file

每当我要将程序生成的输出保存到文件时,都会执行此操作。我也知道此IO重定向的两个变体:

  • someprogram 2>output.of.stderr.file (对于stderr)
  • someprogram &>output.stderr.and.stdout.file (对于stdout + stderr组合)

今天,我遇到了一个我认为不可能的情况。我使用以下命令,xinput test 10并且按预期,我将获得以下输出:

user @ hostname:〜$ xinput测试10
按键30 
密钥释放30 
按键40 
钥匙扣40 
按键32 
钥匙扣32 
按键65 
钥匙扣65 
按键61 
键释放61 
按键31 
^ C
用户@主机名:〜$ 

我希望可以像往常一样将此输出保存到文件中xinput test 10 > output.file。但是,当与我的预期相反时,文件output.file仍然为空。这也是为了xinput test 10 &> output.file确保我不会错过stdout或stderr上的某些内容。

我真的很困惑,因此在这里问xinput程序是否可以避免其输出被重定向?

更新

我看了看资料。似乎此代码生成了输出(请参见下面的代码段)。在我看来,输出将由普通的printf生成

//在文件test.c中

静态无效print_events(Display * dpy)
{
    XEvent事件;

    while(1){
    XNextEvent(dpy,&Event);

    // [...此处省略了其他一些事件类型...]

        如果((Event.type == key_press_type)||
           (Event.type == key_release_type)){
        int循环;
        XDeviceKeyEvent * key =(XDeviceKeyEvent *)&Event;

        printf(“ key%s%d”,(Event.type == key_release_type)?“ release”:“按”,key-> keycode);

        for(loop = 0; loopaxes_count; loop ++){
        printf(“ a [%d] =%d”,key-> first_axis + loop,key-> axis_data [loop]);
        }
        printf(“ \ n”);
    } 
    }
}

我对此进行了修改(请参见下面的下一个代码片段),这使我可以在stderr上复制输出。我可以重定向此输出:

 //在文件test.c中

静态无效print_events(Display * dpy)
{
    XEvent事件;

    while(1){
    XNextEvent(dpy,&Event);

    // [...此处省略了其他一些事件类型...]

        如果((Event.type == key_press_type)||
           (Event.type == key_release_type)){
        int循环;
        XDeviceKeyEvent * key =(XDeviceKeyEvent *)&Event;

        printf(“ key%s%d”,(Event.type == key_release_type)?“ release”:“按”,key-> keycode);
        fprintf(stderr,“ key%s%d”,(Event.type == key_release_type)?“ release”:“ press”,key-> keycode);

        for(loop = 0; loopaxes_count; loop ++){
        printf(“ a [%d] =%d”,key-> first_axis + loop,key-> axis_data [loop]);
        }
        printf(“ \ n”);
    } 
    }
}

目前,我的想法是,也许通过执行重定向程序会失去监视按键释放事件的能力。

Answers:


55

只是当stdout不是终端时,输出才被缓冲。

当您按时Ctrl-C,该缓冲区会丢失(如果尚未写入)。

使用,您会得到相同的行为stdio。例如尝试:

grep . > file

输入几行非空行,然后按Ctrl-C,您将看到文件为空。

另一方面,键入:

xinput test 10 > file

并在键盘上键入足够的缓冲区以使其满(至少要输出4k),您会看到文件大小每次增加4k。

使用grep,您可以键入Ctrl-Dfor grep在刷新其缓冲区后正常退出。对于xinput,我认为没有这样的选择。

请注意,默认情况下stderr未缓冲,这说明了为什么您使用fprintf(stderr)

如果在中xinput.c添加signal(SIGINT, exit),告诉xinput它在收到时可以正常退出SIGINT,那么您将看到file不再为空(假设它不会崩溃,因为不能保证从信号处理程序调用库函数是安全的:请考虑一下如果在printf写入缓冲区时信号进入,可能会发生)。

如果可用,则可以使用以下stdbuf命令来更改stdio缓冲行为:

stdbuf -oL xinput test 10 > file

这个站点上许多问题涉及禁用stdio类型的缓冲,您将在其中找到更多替代解决方案。


2
哇:)做到了。谢谢。所以最后我对这个问题的看法是错误的。没有禁止隐式重定向的地方,很简单Ctrl-C在刷新数据之前将其停止。谢谢您
humanityANDpeace

是否有办法防止stdout的缓冲?
humanityANDpeace

1
@Stephane Chazelas:非常感谢您的详细解释。除了您已经说过的内容外,我还发现可以使用将缓冲区设置为unbuffered setvbuf(stdout, (char *) NULL, _IONBF, NULL)。也许这也很有趣!?
user1146332 2012年

4
@ user1146332,是的,这是正确的stdbuf -o0,同时stdbug -oL恢复缓冲,就像输出到终端一样。stdbuf确实迫使应用程序setvbuf使用LD_PRELOAD技巧进行调用。
斯特凡Chazelas

另一个workaroudn: unbuffer test 10 > fileunbuffer是的一部分expect的工具)
奥利弗杜拉克

23

命令可以直接写入以/dev/tty防止发生定期重定向。

$ cat demo
#!/bin/ksh
LC_ALL=C TZ=Z date > /dev/tty
$ ./demo >demo.out 2>demo.err
Fri Dec 28 10:31:57  2012
$ ls -l demo*
-rwxr-xr-x 1 jlliagre jlliagre 41 2012-12-28 11:31 demo
-rw-r--r-- 1 jlliagre jlliagre  0 2012-12-28 11:31 demo.err
-rw-r--r-- 1 jlliagre jlliagre  0 2012-12-28 11:31 demo.out

您的示例使观点+回答了问题。对的,这是可能的。程序这样做当然是“意外的”和不受欢迎的做法,这至少使我不考虑这种事情的可能性愚弄了。user1146332的答案似乎也是一种避免重定向的令人信服的方法。公平地说,由于两个给定的答案都是避免将命令行程序输出重定向到文件的同等可能的方法,因此我无法选择我猜到的任何答案:(。我需要被允许选择两个答案。谢谢!
humanityANDpeace

1
FTR,如果要捕获在/dev/ttyLinux系统上写入的输出,请使用script -c ./demo demo.log(from util-linux)。
ndim

如果您不是在tty中运行,而是在pty中运行,则可以通过查看procfs(/ proc / $ PID / fd / 0等)来找到。要写入适当的pty,请转到父进程的fd目录,并查看它是否是/ dev / pts / [0-9] +的符号链接。然后,您写入该设备(如果不是pts,则递归)。
dhasenan

9

看起来好像xinput拒绝输出到文件,但不拒绝输出到终端。为此,可能xinput使用系统调用

int isatty(int fd)

检查要打开的文件描述符是否指向终端。

不久前,我通过一个名为的程序偶然发现了相同的现象dpic。在查看了源代码并进行了一些调试之后,我删除了与之相关的行,isatty并且所有操作均再次按预期进行。

但是我同意你的观点,这种经历非常令人不安;)


我真的以为我有自己的解释。但是(1)查看源代码(xinput源代码包中的test.c文件),似乎没有isatty完成测试。输出是由printf函数(我认为它是标准C)生成的。我添加了一些fprintf(stderr,"output"),这可以重定向+证明在xinput的情况下整个代码确实可以运行。谢谢您的建议,毕竟这是第一步。
humanityANDpeace

0

test.c文件中,您可以(void)fflush(stdout);printf语句之后直接使用刷新缓冲的数据。

    // in test.c
    printf("key %s %d ", (Event.type == key_release_type) ? "release" : "press  ", key->keycode);
    //fprintf(stderr,"key %s %d ", (Event.type == key_release_type) ? "release" : "press  ", key->keycode);
    //(void)fflush(NULL);
    (void)fflush(stdout);

在命令行上,您可以通过xinput test 10在伪终端(pty)中运行命令来启用行缓冲输出script

script -q /dev/null xinput test 10 > file      # FreeBSD, Mac OS X
script -c "xinput test 10" /dev/null > file    # Linux

-1

是。当我使用Pascal进行编程时,我什至在DOS时代进行了此操作。我猜原则仍然成立:

  1. 关闭标准输出
  2. 重新打开标准输出作为控制台
  3. 将输出写入标准输出

这确实打断了所有管道。


“重新打开stdout”:stdout被定义为文件描述符1。您可以重新打开文件描述符1,但是您将打开哪个文件?你可能意味着打开终端,在这种情况下,它并不重要程序是否被写入的fd 1
吉尔“所谓停止作恶”

据我记得,@ Gilles的文件是“ con:”-但是,是的,我朝着这个方向完善了第二点。
尼尔斯

con是unix调用的DOS名称/dev/tty,即(控制)终端。
吉尔斯(Gilles)'所以
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.