Você pode enganar isatty AND log stdout e stderr separadamente?

Problema

Portanto, você deseja registrar o stdout e o stderr (separadamente) de um processo ou subprocesso, sem que a saída seja diferente da que você veria no terminal se não estivesse registrando nada.

Parece bastante simples, não? Infelizmente, parece que talvez não seja possível escrever uma solução geral para esse problema, que funcione em qualquer processo ...

fundo

O redirecionamento de canal é um método para separar stdout e stderr, permitindo que você os registre individualmente. Infelizmente, se você alterar o stdout / err para um pipe, o processo poderá detectar que o pipe não é um tty (porque não possui largura / altura, taxa de transmissão, etc.) e pode alterar seu comportamento de acordo. Por que mudar o comportamento? Bem, alguns desenvolvedores usam recursos de um terminal que não fazem sentido se você estiver gravando em um arquivo. Por exemplo, as barras de carregamento geralmente exigem que o cursor do terminal seja movido de volta para o início da linha e a barra de carregamento anterior seja substituída por uma barra de um novo comprimento. Também a cor e o peso da fonte podem ser exibidos em um terminal, mas em um arquivo ASCII simples, eles não podem. Se você escrever o stdout de um programa diretamente em um arquivo, essa saída conterá todos os códigos de escape ANSI do terminal, em vez da saída formatada corretamente. O desenvolvedor, portanto, implementa algum tipo de verificação "isatty" antes de escrever qualquer coisa no stdout / err, para que possa fornecer uma saída mais simples para os arquivos se essa verificação retornar falsa.

A solução usual aqui é convencer esses programas a pensar que os pipes são realmente ttys usando um pty - um pipe bidirecional que também possui largura, altura etc. Você redireciona todas as entradas / saídas do processo para esse pty e isso engana os processo para pensar em falar com um terminal real (e você pode registrá-lo diretamente em um arquivo). O único problema é que, usando um único pty para stdout e stderr, agora não podemos mais diferenciar os dois.

Portanto, você pode tentar um pty diferente para cada canal - um para o stdin, um para o stdout e outro para o stderr. Embora isso funcione 50% do tempo, infelizmente, muitos processos realizam verificações adicionais de redirecionamento que garantem que o caminho de saída do stdout e stderr (/ dev / tty000x) seja o mesmo. Se não estiverem, deve haver um redirecionamento, portanto, eles oferecem o mesmo comportamento como se você tivesse canalizado o stderr e o stdout sem um pty.

Você pode pensar que essa verificação exagerada de redirecionamento é incomum, mas infelizmente é bastante prevalente, porque muitos programas reutilizam outro código para verificação, como este código encontrado no OSX:

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

Desafio

Eu acho que a melhor maneira de encontrar uma solução é na forma de um desafio. Se alguém puder executar o script a seguir (idealmente via Python, mas neste momento aceitarei qualquer coisa) de forma que o stdout e o stderr sejam registrados separadamente, E você conseguiu enganá-lo pensando que foi executado por meio de um tty, você resolve o problema :)

#!/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()

Observe que uma solução realmente deve funcionar paraqualquer processo, não apenas esse código especificamente. Sobrescrever o módulo sys / os e usar LD_PRELOAD é uma maneira muito interessante de superar o desafio, mas eles não resolvem o coração do problema :)

questionAnswers(5)

yourAnswerToTheQuestion