你能愚弄isatty并分别记录stdout和stderr吗?

2024-05-19 01:49:45 发布

您现在位置:Python中文网/ 问答频道 /正文

问题

因此,您需要记录一个进程或子进程的stdout和stderr(单独),而输出与您在没有记录任何内容的终端中看到的输出不同。在

看起来很简单不?不幸的是,它似乎不可能为这个问题写一个通用的解决方案,它适用于任何给定的过程。。。在

背景

管道重定向是分离stdout和stderr的一种方法,允许分别记录它们。不幸的是,如果将stdout/err更改为管道,进程可能会检测到管道不是tty(因为它没有宽度/高度、波特率等),并可能相应地更改其行为。为什么要改变行为?好吧,有些开发人员利用终端的特性,如果你在写文件,这些特性就没有意义了。例如,加载条通常要求将终端光标移回行首,并用新长度的条覆盖上一个加载条。也可以在终端中显示颜色和字体粗细,但在一个简单的ASCII文件中则不能。如果要将这样一个程序的stdout直接写入一个文件,那么输出将包含所有终端ANSI转义码,而不是正确格式化的输出。因此,在向stdout/err写入任何内容之前,开发人员实现了某种“isatty”检查,因此如果检查返回false,它可以为文件提供更简单的输出。在

这里通常的解决方案是通过使用pty(一种双向管道,也有宽度、高度等)来欺骗这些程序,使其认为管道实际上是tty。您可以将进程的所有输入/输出重定向到此pty,这会使进程认为它在与一个真正的终端(并且可以直接将其记录到一个文件中)。唯一的问题是,通过对stdout和stderr使用一个pty,我们现在不能再区分这两者了。在

因此,您可能需要为每个管道尝试不同的pty—一个用于stdin,一个用于stdout,一个用于stderr。虽然这将在50%的时间内有效,但不幸的是,许多进程会执行额外的重定向检查,以确保stdout和stderr(/dev/tty000x)的输出路径相同。如果不是,则必须进行重定向,因此它们提供的行为与在没有pty的情况下通过管道传输stderr和stdout相同。在

您可能会认为这种过度检查重定向是不常见的,但不幸的是,它实际上相当普遍,因为许多程序都会重复使用其他代码进行检查,例如在OSX中找到的以下代码:

http://src.gnu-darwin.org/src/bin/stty/util.c

挑战

我认为找到解决办法的最好办法是挑战。如果有人可以运行以下脚本(理想情况下是通过Python,但在这一点上,我将采用任何方法),使stdout和stderr分别记录,并且您设法使它认为它是通过tty执行的,那么问题就解决了:)

#!/usr/bin/python

import os
import sys

if sys.stdout.isatty() and sys.stderr.isatty() and os.ttyname(sys.stdout.fileno()) == os.ttyname(sys.stderr.fileno()):
    sys.stdout.write("This is a")
    sys.stderr.write("real tty :)")
else:
    sys.stdout.write("You cant fool me!")

sys.stdout.flush()
sys.stderr.flush()

请注意,解决方案应该真正适用于任何进程,而不仅仅是这段代码。重写sys/os模块和使用LD斨u PRELOAD是一种非常有趣的战胜挑战的方法,但它们并不能解决问题的核心:)


Tags: 文件方法程序终端管道进程osstderr
3条回答

像这样?在

% ./challenge.py >stdout 2>stderr
% cat stdout 
This is a real tty :)
standard output data
% cat stderr 
standard error data

因为我有点作弊。;—)

^{pr2}$

像这样。。。在

% gcc preload.c -shared -o preload.so -fPIC

我现在觉得脏兮兮的,但很有趣。:天

% cat preload.c
#include <stdlib.h>

int isatty(int fd) {
    if(fd == 2 || fd == 1) {
        return 1;
    }
    return 0;
}

char* ttyname(int fd) {
    static char* fake_name = "/dev/fake";
    if(fd == 2 || fd == 1) {
        return fake_name;
    }
    return NULL;
}

您总是可以分配伪TTY,这就是screen所做的。在

在Python中,可以使用pty.openpty()来访问它

此“主”代码通过测试:

import subprocess, pty, os

m, s = pty.openpty()
fm = os.fdopen(m, "rw")
p = subprocess.Popen(["python2", "test.py"], stdin=s, stdout=s, stderr=s)
p.communicate()
os.close(s)
print fm.read()

当然,如果您想区分stdin/out/err,“slave”进程将看到不同的PYT名称:

^{pr2}$

对于更简单的用例(例如开发测试),使用strace(linux)或dtruss(OSX)。当然,这在特权进程中是行不通的。在

这是一个示例,您可以区分stdoutfd1和{}fd2:

$ strace -ewrite python2 test.py
[snip]
write(1, "This is a real tty :)\n", 22This is a real tty :)
) = 22
write(2, "standard error data", 19standard error data)     = 19
write(1, "standard output data", 20standard output data)    = 20
+++ exited with 0 +++

在上面的示例中,您可以看到每个standard xxx data加倍,因为您不能重定向stdout/stderr。但是,您可以要求strace将其输出保存到文件中。在

从理论上讲,如果stdout和{}指的是同一个终端,那么在进程上下文中,无论是在用户模式(LD_PRELOAD),还是在内核空间(strace工具使用的ptrace接口)中,您只能区分这两个终端。一旦数据击中实际设备,真实的或伪的,区别就失去了。在

相关问题 更多 >

    热门问题