在正在运行的程序中强制输出缓冲区刷新


20

我有一个长期运行的python脚本,该脚本定期将数据输出到我用以下命令调用的标准输出:

python script.py > output.txt

该脚本已经运行了一段时间,我想用Ctrl+ 停止它,C但不要丢失任何输出。不幸的是,当我实现脚本时,我忘了在输出的每一行之后都用诸如sys.stdout.flush()以前建议的用于强制输出刷新的解决方案)之类的东西来刷新缓冲区,因此现在调用Ctrl+ C将导致我丢失所有输出。

如果想知道是否有任何方法可以与正在运行的python脚本(或更一般而言,正在运行的进程)进行交互以强制其刷新其输出缓冲区。我不是在问如何编辑和重新运行脚本以使其正确刷新-这个问题专门用于与正在运行的进程进行交互(在我的情况下,不会丢失当前代码执行的输出)。

Answers:


18

如果确实需要数据,我建议将gdb调试器附加到python解释器,暂时停止任务,调用fsync(1)stdout),从中分离(恢复过程)并仔细阅读输出文件。

查看/proc/$(pidof python)/fd以查看有效的文件描述符。$(pidof x)返回名为“ x” 的进程的PID 。

# your python script is running merrily over there.... with some PID you've determined.
#
# load gdb
gdb
#
# attach to python interpreter (use the number returned by $(pidof python))
attach 1234
#
# force a sync within the program's world (1 = stdout, which is redirected in your example)
call fsync(1)
#
# the call SHOULD have returned 0x0, sync successful.   If you get 0xffffffff (-1), perhaps that wasn't stdout.  0=stdin, 1=stdout, 2=stderr
#
# remove our claws from poor python
detach
#
# we're done!
quit

我已经使用这种方法来更改工作目录,即时调整设置...很多事情。las,您只能调用正在运行的程序中定义的函数,fsync但是效果很好。

(gdb命令“ info functions”将列出所有可用的功能。但是请小心。您正在一个进程上进行LIVE操作。)

还有一个命令peekfdpsmisc在Debian Jessie和其他产品上的软件包中找到),该命令使您可以查看进程缓冲区中隐藏的内容。再次,/proc/$(pidof python)/fd将显示有效的文件描述符作为peekfd的参数。

如果您不记得-u使用python,则始终可以在命令前面加上stdbuf(in coreutils,已经安装),以根据需要将stdin / stdout / stderr设置为无缓冲,行缓冲或块缓冲:

stdbuf -i 0 -o 0 -e 0 python myscript.py > unbuffered.output

当然,man pages是您的朋友,嘿!也许别名在这里也可能有用。

alias python='python -u'

现在,您的python总是-u用于所有命令行工作!


5

首先,请确保您具有用于Python(或至少是glibc)的调试符号。在Fedora 1上,您可以使用以下方法安装它们:

dnf debuginfo-install python

然后将gdb附加到正在运行的脚本并运行以下命令:

[user@host ~]$ pidof python2
9219
[user@host ~]$ gdb python2 9219
GNU gdb (GDB) Fedora 7.7.1-13.fc20
...
0x00007fa934278780 in __read_nocancel () at ../sysdeps/unix/syscall-template.S:81
81  T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
(gdb) call fflush(stdout)
$1 = 0
(gdb) call setvbuf(stdout, 0, 2, 0)
$2 = 0
(gdb) quit
A debugging session is active.

    Inferior 1 [process 9219] will be detached.

Quit anyway? (y or n) y
Detaching from program: /usr/bin/python2, process 9219

这将刷新标准输出并禁用缓冲。在2setvbuf呼叫的价值_IONBF我的系统上。您需要找出您的内容(grep _IONBF /usr/include/stdio.h应该可以解决问题)。

根据我在CPython 2.7 PyFile_SetBufSizePyFile_WriteStringCPython 2.7 的实现中所看到的内容,它应该可以很好地工作,但是我不能做任何保证。


1 Fedora包含一种特殊的RPM类型,称为debuginfo rpms。这些自动创建的RPM包含来自程序文件的调试信息,但已移至外部文件中。


我尝试了python 2.7,最终得到了相同的结果。我将看一下您发布的调试更新。
DarkHeart '16

就其价值而言,CPython 3.5似乎具有fileobject.c2.7不同的I / O()实现。有人需要深入研究该io模块。
Cristian Ciupitu

@DarkHeart,您可能想先使用一个像这样的简单程序进行测试。
克里斯蒂安·丘皮图

4

您无法立即解决问题。如果脚本已经启动,则事后便无法更改缓冲模式。这些都是内存缓冲区,并且所有这些都是在脚本启动,打开文件句柄,创建管道等时设置的。

从长远来看,当且仅当在输出的IO级别上正在执行某些或所有相关的缓冲时,您才可以执行sync命令;但这在这种情况下通常不太可能。

将来,您可以使用Python的-u选项*运行脚本。通常,许多命令具有特定于命令的选项来禁用stdin / stdout缓冲,并且您还可以使用软件包中的unbuffer命令获得一些通用的成功expect

A Ctrl+ C会导致程序中断时刷新系统级缓冲区,除非该缓冲区由Python本身完成,并且尚未实现使用Ctrl+ 刷新其自身缓冲区的逻辑C。暂停,崩溃或终止不会那么好。

*强制完全禁用stdin,stdout和stderr。


2

Python 2.7.7文档的“ Python设置和使用”部分,第1小节 。命令行和环境描述了此Python参数:

-u

强制stdin,stdout和stderr完全没有缓冲。在重要的系统上,还将stdin,stdout和stderr置于二进制模式。

请注意,file.readlines()和File Objects(用于sys.stdin中的行)中有内部缓冲,不受此选项的影响。要解决此问题,您将需要在while 1:循环内使用file.readline()。

还有这个环境变量:

PYTHONUNBUFFERD

如果将其设置为非空字符串,则等效于指定-u选项。


1
谢谢-但这两个听起来都像是我第一次运行python脚本时需要指定的选项。我想知道是否有办法让正在运行的脚本转储其输出。
josliber

我不相信有这样的解决方案,因为数据可能在某个地方的内存缓冲区中。您需要将一个dll注入python,该DLL非常了解其可执行文件,才能知道缓冲区在哪里以及如何将其写出。我相信大多数人只会使用以上两种方法之一。毕竟,添加环境变量非常容易。
harrymc 2014年

好的,很高兴知道可能没有解决方案。如我的问题所述,我知道如何在python中刷新缓冲区(我本来会使用sys.stdout.flush(),但是您的-u选择似乎更简单),但是在调用我的代码时却忘记这样做了。我已经运行了一个多星期的代码,我希望有一种方法可以获取我的输出,而无需再运行另一个星期的代码。
josliber 2014年

如果您知道数据是什么样的话,一个牵强的方法是使用Process Explorer进行进程的完整内存转储,然后在文件中搜索字符串。这不会终止该过程,因此您仍然可以尝试其他方法。
harrymc 2014年

我在linux上-有与该软件相对应的linux吗?
josliber 2014年

2

似乎我对运行Ctrl-C后缓冲输出丢失过于谨慎;根据这篇文章,如果我的程序正常退出,我应该期望缓冲区被刷新,如果我按Ctrl-C就是这种情况。另一方面,如果我使用SIGKILL或类似命令杀死脚本,则会丢失缓冲的输出。


您必须尝试找出答案。Ctrl-C将导致刷新低级IO缓冲区。如果Python自己执行缓冲,那么只有在Python足够友好地执行逻辑操作时,Ctrl-C才会刷新它们。希望Python决定不重新发明轮子,而是依靠系统的正常缓冲水平。我不知道是不是这样。但是要注意。
詹森·C

操作系统永远无法清除程序内存空间中的内容。刷新的是系统内存中的数据,这意味着程序已使用系统调用将其写出。在错误退出的情况下,即使这些系统缓冲区也将被丢弃。简而言之,尚未被Python写入的数据无法刷新,并且在所有情况下都将丢失。
harrymc 2014年

0

我认为另一种可能的解决方案可以是通过丢弃内核来强制终止进程,然后分析死后的内存内容。

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.