Como induzir um aplicativo a pensar que seu stdout é um terminal, não um pipe


147

Estou tentando fazer o oposto de " Detectar se stdin é um terminal ou tubo? ".

Estou executando um aplicativo que está alterando seu formato de saída porque detecta um canal no STDOUT e quero que ele pense que é um terminal interativo para que eu obtenha a mesma saída ao redirecionar.

Eu estava pensando que envolvê-lo em um expectscript ou usar um proc_open()no PHP faria isso, mas não o faz.

Alguma idéia por aí?


8
O empty.sf.net ajuda?
ephemient

1
@hemient: deveria ter sido uma resposta. Grande util pelo caminho ...
neuro

A pergunta fala sobre stdout, mas o título menciona stdin. Eu acho que o título está errado.
Piotr Dobrogost

Respostas:


177

Aha!

O scriptcomando faz o que queremos ...

script --return --quiet -c "[executable string]" /dev/null

Faz o truque!

Usage:
 script [options] [file]

Make a typescript of a terminal session.

Options:
 -a, --append                  append the output
 -c, --command <command>       run command rather than interactive shell
 -e, --return                  return exit code of the child process
 -f, --flush                   run flush after each write
     --force                   use output file even when it is a link
 -q, --quiet                   be quiet
 -t[<file>], --timing[=<file>] output timing data to stderr or to FILE
 -h, --help                    display this help
 -V, --version                 display version

1
+1: apenas tropeçar no problema com uma lib que faz inicialização estática. Uma mudança recente no Fedora 12 fez com que o init falhasse quando o exe não estava em um tty. Seu truque funciona perfeitamente. Eu o preferi ao invés do buffer porque o script é instalado por padrão!
Neuro

scriptestá disponível até no BusyBox !
Dolmen

9
Se você deseja canalizá-lo para algo interativo, como para less -Ronde a entrada do terminal vai less -R, então precisará de mais truques. Por exemplo, eu queria uma versão colorida de git status | less. Você precisa passar -Rpara menos para respeitar as cores e usar scriptpara obter git statuscores de saída. Mas não queremos scriptmanter a propriedade do teclado, queremos que isso ocorra less. Então, eu uso isso agora e ele funciona bem: 0<&- script -qfc "git status" /dev/null | less -R . Esses primeiros caracteres fecham stdin para esse único comando.
precisa

2
Nota: Isso não funciona nos casos em que o componente que verifica a interatividade está olhando para a $-variável do shell para um "i".
Jay Taylor

1
Isso é incrível. Eu precisava disso para um caso de uso extremamente raro, com uma biblioteca Python incorporada em um executável executado no Wine. Quando eu rodava em um terminal, ele funcionava, mas quando rodava o arquivo .desktop que eu criava, ele sempre travava porque Py_Initializenão via stdin / stderr adequado.
Tatsh

60

Com base na solução de Chris , criei a seguinte função auxiliar:

faketty() {
    script -qfc "$(printf "%q " "$@")" /dev/null
}

A aparência peculiar printfé necessária para expandir corretamente os argumentos do script $@enquanto protege partes do comando possivelmente citadas (veja o exemplo abaixo).

Uso:

faketty <command> <args>

Exemplo:

$ python -c "import sys; print sys.stdout.isatty()"
True
$ python -c "import sys; print sys.stdout.isatty()" | cat
False
$ faketty python -c "import sys; print sys.stdout.isatty()" | cat
True

9
Você provavelmente deseja usar a --returnopção, se a sua versão scriptpossui, para preservar o código de saída do processo filho.
jwd

5
Eu recomendo alterar essa função da seguinte forma: function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } caso contrário, um arquivo nomeado typescriptserá criado sempre que um comando for executado, em muitos casos.
W0rp 3/03

1
parece não funcionar no MacOS tho, eu entendo script: illegal option -- f
Alexander Mills

23

O script unbuffer que acompanha o Expect deve lidar com isso ok. Caso contrário, o aplicativo pode estar procurando algo diferente do que sua saída está conectada, por exemplo. para o qual a variável de ambiente TERM está configurada.


17

Não sei se é factível a partir do PHP, mas se você realmente precisar do processo filho para ver um TTY, poderá criar um PTY .

Em C:

#include <stdio.h>
#include <stdlib.h>
#include <sysexits.h>
#include <unistd.h>
#include <pty.h>

int main(int argc, char **argv) {
    int master;
    struct winsize win = {
        .ws_col = 80, .ws_row = 24,
        .ws_xpixel = 480, .ws_ypixel = 192,
    };
    pid_t child;

    if (argc < 2) {
        printf("Usage: %s cmd [args...]\n", argv[0]);
        exit(EX_USAGE);
    }

    child = forkpty(&master, NULL, NULL, &win);
    if (child == -1) {
        perror("forkpty failed");
        exit(EX_OSERR);
    }
    if (child == 0) {
        execvp(argv[1], argv + 1);
        perror("exec failed");
        exit(EX_OSERR);
    }

    /* now the child is attached to a real pseudo-TTY instead of a pipe,
     * while the parent can use "master" much like a normal pipe */
}

Na verdade, eu estava com a impressão de que expectele cria um PTY.


Você sabe como executar o nettop como o processo filho no mac os x? Quero obter a saída do nettop no meu aplicativo. Tentei usar o forkpty, mas ainda não consegui executar o nettop com êxito.
Vince Yuan

16

Referindo a resposta anterior, no Mac OS X, "script" pode ser usado como abaixo ...

script -q /dev/null commands...

Mas, como ele pode substituir "\ n" por "\ r \ n" no stdout, você também pode precisar de um script como este:

script -q /dev/null commands... | perl -pe 's/\r\n/\n/g'

Se houver algum canal entre esses comandos, você precisará liberar o stdout. por exemplo:

script -q /dev/null commands... | ruby -ne 'print "....\n";STDOUT.flush' |  perl -pe 's/\r\n/\n/g'

1
Obrigado pela sintaxe do OS X, mas, a julgar pela sua declaração Perl, parece que você quis dizer que ela altera instâncias de "\ r \ n" para "\ n", e não o contrário, correto?
mklement0

8

É novo demais para comentar sobre a resposta específica, mas pensei em fakettyseguir a função postada pela ingomueller-net acima, pois ela recentemente me ajudou.

Eu descobri que isso estava criando um typescriptarquivo que eu não queria / precisava, então adicionei / dev / null como o arquivo de destino do script:

function faketty { script -qfc "$(printf "%q " "$@")" /dev/null ; }


3

Atualizando a resposta de @ A-Ron para a) trabalho no Linux e MacOs b) propagar o código de status indiretamente (já que o MacOs scriptnão o suporta)

faketty () {
  # Create a temporary file for storing the status code
  tmp=$(mktemp)

  # Ensure it worked or fail with status 99
  [ "$tmp" ] || return 99

  # Produce a script that runs the command provided to faketty as
  # arguments and stores the status code in the temporary file
  cmd="$(printf '%q ' "$@")"'; echo $? > '$tmp

  # Run the script through /bin/sh with fake tty
  if [ "$(uname)" = "Darwin" ]; then
    # MacOS
    script -Fq /dev/null /bin/sh -c "$cmd"
  else
    script -qfc "/bin/sh -c $(printf "%q " "$cmd")" /dev/null
  fi

  # Ensure that the status code was written to the temporary file or
  # fail with status 99
  [ -s $tmp ] || return 99

  # Collect the status code from the temporary file
  err=$(cat $tmp)

  # Remove the temporary file
  rm -f $tmp

  # Return the status code
  return $err
}

Exemplos:

$ faketty false ; echo $?
1

$ faketty echo '$HOME' ; echo $?
$HOME
0

embedded_example () {
  faketty perl -e 'sleep(5); print "Hello  world\n"; exit(3);' > LOGFILE 2>&1 </dev/null &
  pid=$!

  # do something else
  echo 0..
  sleep 2
  echo 2..

  echo wait
  wait $pid
  status=$?
  cat LOGFILE
  echo Exit status: $status
}

$ embedded_example
0..
2..
wait
Hello  world
Exit status: 3

2

Eu estava tentando obter cores durante a execução shellcheck <file> | less, então tentei as respostas acima, mas elas produzem esse efeito bizarro em que o texto é deslocado horizontalmente de onde deveria estar:

In ./all/update.sh line 6:
                          for repo in $(cat repos); do
                                                                  ^-- SC2013: To read lines rather than words, pipe/redirect to a 'while read' loop.

(Para aqueles que não estão familiarizados com a verificação de shell, a linha do aviso deve estar alinhada com a localização do problema.)

Para que as respostas acima funcionem com o shellcheck, tentei uma das opções dos comentários:

faketty() {                       
    0</dev/null script -qfc "$(printf "%q " "$@")" /dev/null
}

Isso funciona. Também adicionei --returne usei opções longas, para tornar este comando um pouco menos inescrutável:

faketty() {                       
    0</dev/null script --quiet --flush --return --command "$(printf "%q " "$@")" /dev/null
}

Trabalha em Bash e Zsh.


1

Há também um programa pty incluído no código de exemplo do livro "Programação Avançada no Ambiente UNIX, Segunda Edição"!

Veja como compilar pty no Mac OS X:

man 4 pty  #  pty -- pseudo terminal driver

open http://en.wikipedia.org/wiki/Pseudo_terminal

# Advanced Programming in the UNIX Environment, Second Edition
open http://www.apuebook.com

cd ~/Desktop

curl -L -O http://www.apuebook.com/src.tar.gz

tar -xzf src.tar.gz

cd apue.2e

wkdir="${HOME}/Desktop/apue.2e"

sed -E -i "" "s|^WKDIR=.*|WKDIR=${wkdir}|" ~/Desktop/apue.2e/Make.defines.macos

echo '#undef _POSIX_C_SOURCE' >> ~/Desktop/apue.2e/include/apue.h

str='#include   <sys/select.h>'
printf '%s\n' H 1i "$str" . wq | ed -s calld/loop.c

str='
#undef _POSIX_C_SOURCE
#include <sys/types.h>
'
printf '%s\n' H 1i "$str" . wq | ed -s file/devrdev.c

str='
#include <sys/signal.h>
#include <sys/ioctl.h>
'
printf '%s\n' H 1i "$str" . wq | ed -s termios/winch.c

make

~/Desktop/apue.2e/pty/pty ls -ld *

Erro realmente estranho também: Erro rápido: domínio desconhecido: codesnippets.joyent.com. Verifique se este domínio foi adicionado a um serviço.
I336_
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.