【问题标题】:Can you fool isatty AND log stdout and stderr separately?你能愚弄 isatty 并分别记录 stdout 和 stderr 吗?
【发布时间】:2016-03-15 03:39:46
【问题描述】:

问题

因此,您希望(分别)记录一个进程或子进程的 stdout 和 stderr,如果您没有记录任何内容,则输出不会与您在终端中看到的不同。

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

背景

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

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

因此,您可能希望为每个管道尝试不同的 pty - 一个用于标准输入,一个用于标准输出,一个用于标准错误。虽然这将在 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_PRELOAD 是克服挑战的非常有趣的方法,但它们并没有解决问题的核心:)

【问题讨论】:

  • 您可能希望通过在 Q 中添加赏金来增加成功的机会。祝您好运!
  • 好主意!我会在 2 天的冷却期到期后做 :)
  • 我不会称这为“愚弄 isatty()”,而是愚弄 stty 的 checkredirect(),这要严格得多。
  • 谢谢@CharlesDuffy,我已更新问题以更好地反映我正在寻找适用于任何给定流程的通用解决方案,而不仅仅是这段特定的代码。

标签: python linux unix tty pty


【解决方案1】:

像这样?

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

因为我作弊了一点。 ;-)

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

就这样……

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

我现在觉得很脏,但很有趣。 :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;
}

【讨论】:

  • 呵呵呵呵,这太棒了 :D 但这绝对是作弊 :P 如果不仅仅是因为自从 El Capitan 在系统二进制文件中将 DYLD_INSERT_LIBRARIES 从我们手中夺走后它就无法在 OSX 上运行 :((
  • 是的,好吧,我缺乏其他想法。除了做自己的内核工作。无论如何,您将不得不在这里“作弊”。 :D
  • 在 Python 中重新定义这些函数更加容易。但是话又说回来,脚本可以反省它们并找出...
  • 是的,但是 Python 应该是一个简化的挑战,而真正要解决的问题是二进制文件。
【解决方案2】:

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

这里有一个示例,你可以区分stdout fd1 和stderr 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 将其输出保存到文件中。

在理论上,如果stdoutstderr 指的是同一个终端,你只能在你的进程上下文中区分这两个,无论是在用户模式(LD_PRELOAD)还是内核空间( strace 工具使用的 ptrace 接口)。一旦数据到达实际设备,无论是真实的还是伪的,区别就消失了。

【讨论】:

  • 使用更高效的工具(如 sysdig)将有助于提高性能。
【解决方案3】:

您始终可以分配伪 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,你的“从”进程会看到不同的PYT名称:

inp = pty.openpty()
oup = pty.openpty()
erp = pty.openpty()

subprocess.Popen([command, args], stdin=inp[1], stdout=uop[1], stderr=erp[1])

【讨论】:

  • 这说明了 OP 中提到的问题 - 您可以欺骗子进程,也可以进行单独的日志记录,但不能同时进行。
  • 如果你创建一个新的/dev/pts 挂载呢?
  • 我不太明白 o11c 是什么意思 - 你能详细说明一下吗? :)
【解决方案4】:

当你可以使用脚本命令时:

$ script --return -c "[executable string]" >stdout 2>stderr

【讨论】:

  • 很遗憾,我无法在 OSX 上对此进行测试,但稍后我会在 Linux 上再试一次并通知您:)
猜你喜欢
  • 2014-07-14
  • 1970-01-01
  • 2013-09-17
  • 1970-01-01
  • 2020-01-27
  • 1970-01-01
  • 1970-01-01
  • 2021-12-04
  • 2018-04-29
相关资源
最近更新 更多