【问题标题】:on-the-fly output redirection, seeing the file redirection output while the program is still running即时输出重定向,在程序仍在运行时查看文件重定向输出
【发布时间】:2011-03-31 13:20:39
【问题描述】:

如果我使用这样的命令:
./program >> a.txt &
,并且该程序是一个长时间运行的程序,然后我只能在程序结束后才能看到输出。这意味着我无法知道计算是否顺利,直到它真正停止计算。我希望能够在程序运行时读取文件中的重定向输出。

这类似于打开一个文件,附加到它,然后在每次写入后关闭它。如果文件仅在程序结束时关闭,则在程序结束之前无法读取任何数据。我知道的唯一重定向类似于在程序结束时关闭文件。

您可以使用这个 Python 小脚本对其进行测试。语言无所谓。任何写入标准输出的程序都有同样的问题。

l = range(0,100000)
for i in l:
  if i%1000==0:
    print i
  for j in l:
    s = i + j

可以通过以下方式运行它:
./python program.py >> a.txt &
然后 cat a.txt .. 只有脚本完成计算才能得到结果。

【问题讨论】:

标签: linux bash


【解决方案1】:

来自stdout manual page

流标准错误是无缓冲的。 流标准输出是行缓冲的 当它指向终端时。 部分线条不会出现,直到 fflush(3) 或 exit(3) 被调用,或者 打印一个换行符。

底线:除非输出是终端,否则默认情况下,您的程序的标准输出将处于完全缓冲模式。这实质上意味着它将以大块的形式输出数据,而不是逐行输出,更不用说逐个字符了。

解决方法:

  • 修复程序:如果需要实时输出,则需要修复程序。在 C 语言中,您可以在每个输出语句后使用fflush(stdout),或使用setvbuf() 更改标准输出的缓冲模式。对于 Python,有 sys.stdout.flush() 甚至一些建议 here

  • 使用可以从 PTY 记录的实用程序,而不是直接的 stdout 重定向。 GNU Screen 可以为你做到这一点:

    screen -d -m -L python test.py
    

    将是一个开始。这会将程序的输出记录到当前目录中名为 screenlog.0(或类似文件)的文件中,默认延迟为 10 秒,您可以使用 screen 连接到运行命令的会话以提供输入或终止它。延迟和日志文件的名称可以在配置文件中更改,也可以在连接到后台会话后手动更改。

编辑:

在大多数 Linux 系统上,还有第三种解决方法:您可以使用 LD_PRELOAD 变量和预加载库来覆盖 C 库的选择函数,并在这些函数被调用时使用它们来设置 stdout 缓冲模式你的程序。这种方法可能有效,但它有许多缺点:

  • 它在静态可执行文件上根本不起作用

  • 它很脆弱,而且相当丑陋。

  • 它根本不适用于 SUID 可执行文件 - 出于安全原因,动态加载程序在加载此类可执行文件时将拒绝读取 LD_PRELOAD 变量。

  • 它很脆弱,而且相当丑陋。

  • 它要求您找到并覆盖由您的程序调用的库函数之后它最初设置stdout 缓冲模式,最好是之前任何输出。 getenv() 是许多程序的不错选择,但不是全部。您可能必须重写常见的 I/O 函数,例如 printf()fwrite() - 如果迫在眉睫,您可能只需要重写所有控制缓冲模式的函数并为 stdout 引入特殊条件。

  • 它很脆弱,而且相当丑陋。

  • 很难确保没有不受欢迎的副作用。要做到这一点,您必须确保只有stdout 受到影响,并且您的覆盖不会使程序的其余部分崩溃,例如stdout 已关闭。

  • 我有没有提到它很脆弱而且相当丑陋?

也就是说,过程比较简单。您放入一个 C 文件,例如linebufferedstdout.c替换函数:

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <dlfcn.h>


char *getenv(const char *s) {
    static char *(*getenv_real)(const char *s) = NULL;

    if (getenv_real == NULL) {
        getenv_real = dlsym(RTLD_NEXT, "getenv");

        setlinebuf(stdout);
    }

    return getenv_real(s);
}

然后将该文件编译为共享对象:

gcc -O2 -o linebufferedstdout.so -fpic -shared linebufferedstdout.c -ldl -lc

然后设置LD_PRELOAD 变量以将其与您的程序一起加载:

$ LD_PRELOAD=./linebufferedstdout.so python test.py | tee -a test.out 
0
1000
2000
3000
4000

如果你幸运的话,你的问题将得到解决,没有不幸的副作用。

如有必要,您可以在 shell 中设置 LD_PRELOAD 库,或者甚至在 /etc/ld.so.preload 中指定系统范围的库(绝对推荐)。

【讨论】:

  • 很好的答案,但是缓冲模式不能更改为适合环境的行吗?这正是我不喜欢做的,改变我所有程序的来源。 :( 这应该是操作系统控制的,你不同意吗? screen 命令可能不好,因为我使用 qsub 将作业提交到大型服务器,或者使用 nohup 提交较小的服务器。但是,我会试试看!
【解决方案2】:

如果您尝试修改现有程序的行为,请尝试使用 stdbuf(显然是从 7.5 版开始的 coreutils 的一部分)。

这会将标准输出缓冲到一行:

stdbuf -oL command &gt; output

这将完全禁用标准输出缓冲:

stdbuf -o0 command &gt; output

【讨论】:

  • 正是我想要的。这是最简单的解决方案,而且很有效。
【解决方案3】:

你考虑过用管道连接到发球台吗?

./program | tee a.txt

但是,如果“程序”在完成之前不向标准输出写入任何内容,那么即使 tee 也无法工作。因此,有效性很大程度上取决于您的程序的行为方式。

【讨论】:

  • 我刚试过./program | tee a.txt 和 ./program | tee -a a.txt。没有任何效果。
  • 程序在结束前会写什么吗?它是否正在刷新其缓冲区?那么简单的./program 会做什么呢?
  • 程序可以是任何东西,只要它把输出放到终端。它可以是 C 程序或 python 脚本等。我在所有这些程序中都遇到了同样的问题。如果我尝试将标准输出重定向到一个文件,我只能在程序结束后读取该文件。
  • 也许输出在 stderr: ./program 2>&1 | tee a.txt
  • 还是不行。无论如何,该程序运行良好,几个月来我对许多其他程序和脚本都有同样的问题。可能涉及到一般原则。
【解决方案4】:

如果程序写入文件,您可以在写入时使用tail -f a.txt 读取它。

【讨论】:

  • 程序写入标准输出而不是文件,标准输出必须重定向到文件。我用上面的小脚本检查了你的命令,但它不起作用。通过附加到文件然后在每次附加后关闭该文件来规避这个问题很简单,但这不是我想要的。我想要的是在文件仍然生成时读取文件上的标准输出重定向。
【解决方案5】:

您的问题是大多数程序会检查输出是否为终端。如果输出是终端,则输出一次缓冲一行(因此每行在生成时都会被输出),但如果输出不是终端,则输出会以更大的块缓冲(一次通常为 4096 个字节) ) 这种行为是 C 库(例如使用 printf 时)和 C++ 库中(例如使用 cout 时)的正常行为,因此任何用 C 或 C++ 编写的程序都会这样做。

大多数其他脚本语言(如 perl、python 等)都是用 C 或 C++ 编写的,因此它们具有完全相同的缓冲行为。

上面的答案(使用 LD_PRELOAD)可以用于 perl 或 python 脚本,因为解释器本身是用 C 编写的。

【讨论】:

    【解决方案6】:

    expect 包中的 unbuffer 命令完全符合您的要求。

    $ sudo apt-get install expect
    $ unbuffer python program.py | cat -
    <watch output immediately show up here>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-14
      • 2021-04-14
      • 1970-01-01
      相关资源
      最近更新 更多