您可以傻傻地呆着并分别记录stdout和stderr吗? [英] Can you fool isatty AND log stdout and stderr separately?

查看:151
本文介绍了您可以傻傻地呆着并分别记录stdout和stderr吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

因此,您想(分别)记录一个进程或子进程的stdout和stderr,如果您未记录任何内容,则输出不会与在终端上看到的输出不同.

So you want to log the stdout and stderr (separately) of a process or subprocess, without the output being different from what you'd see in the terminal if you weren't logging anything.

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

Seems pretty simple no? Well unfortunately, it appears that it may not be possible to write a general solution for this problem, that works on any given process...

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

Pipe redirection is one method to separate stdout and stderr, allowing you to log them individually. Unfortunately, if you change the stdout/err to a pipe, the process may detect the pipe is not a tty (because it has no width/height, baud rate, etc) and may change its behaviour accordingly. Why change the behaviour? Well, some developers make use of features of a terminal which don't make sense if you are writing out to a file. For example, loading bars often require the terminal cursor to be moved back to the beginning of the line and the previous loading bar to be overwritten with a bar of a new length. Also colour and font weight can be displayed in a terminal, but in a flat ASCII file they can not. If you were to write such a program's stdout directly to a file, that output would contain all the terminal ANSI escape codes, rather than properly formatted output. The developer therefore implements some sort of "isatty" check before writing anything to the stdout/err, so it can give a simpler output for files if that check returns false.

通常的解决方案是通过使用pty欺骗此类程序,使其认为管道实际上是ttys-双向管道也具有宽度,高度等.您将过程的所有输入/输出重定向到该pty,并且这会欺骗该过程,使其思考与真实终端的对话(您可以将其直接记录到文件中).唯一的问题是,通过对stdout和stderr使用单一的pty,我们现在不再能够区分两者.

The usual solution here is to trick such programs into thinking the pipes are actually ttys by using a pty - a bidirectional pipe that also has width, height, etc. You redirect all inputs/outputs of the process to this pty, and that tricks the process into thinking its talking to a real terminal (and you can log it directly to a file). The only problem is, that by using a single pty for stdout and stderr, we can now no longer differentiate between the two.

因此,您可能想为每个管道尝试不同的pty-一个用于stdin,一个用于stdout,一个用于stderr.尽管这将在50%的时间中起作用,但不幸的是,许多进程还会执行其他重定向检查,以确保stdout和stderr(/dev/tty000x)的输出路径相同.如果不是,则必须进行重定向,因此,它们的行为就如同您通过管道传输stderr和stdout时一样.

So you might want to try a different pty for each pipe - one for the stdin, one for the stdout, and one for the stderr. While this will work 50% of the time, many processes unfortunately do additional redirection checks that make sure that the output path of the stdout and stderr (/dev/tty000x) are the same. If they are not, there must be redirection, thus they give you the same behaviour as if you had piped the stderr and stdout without a pty.

您可能会认为这种对重定向的全面检查并不常见,但是不幸的是,它实际上非常普遍,因为许多程序都使用其他代码进行检查,例如OSX中的以下代码:

You might think this over-the-top checking for redirection is uncommon, but unfortunately it is actually quite prevalent because a lot of programs re-use other code for checking, like this bit of code found in OSX:

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

挑战

我认为找到解决方案的最好方法是挑战.如果任何人都可以通过以下方式运行以下脚本(最好是通过Python运行,但此时我会采取任何措施):将stdout和stderr分别记录下来,而您却愚弄了它以为它是通过tty执行的,您解决了问题:)

Challenge

I think the best way to find a solution is in the form of a challenge. If anyone can run the following script (ideally via Python, but at this point I'll take anything) in such a way that the stdout and stderr is logged separately, AND you managed to fool it into thinking it was executed via a tty, you solve the problem :)

#!/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_PRELOAD是克服挑战的非常有趣的方法,但是它们不能解决问题的核心:)

Note that a solution should really work for any process, not just this code specifically. Overwriting the sys/os module and using LD_PRELOAD is very interesting ways to beat the challenge, but they don't solve the heart of the problem :)

推荐答案

喜欢吗?

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

因为我有点作弊. ;-)

Because I cheated a little bit. ;-)

% echo $LD_PRELOAD
/home/karol/preload.so

就这样...

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

我现在觉得很脏,但这很有趣. :D

I feel dirty now, but it was fun. :D

% 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;
}

这篇关于您可以傻傻地呆着并分别记录stdout和stderr吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆