Как заставить приложение думать, что его стандартный вывод - это терминал, а не труба

147

Я пытаюсь сделать противоположное « Определить, является ли stdin терминалом или каналом? ».

Я запускаю приложение, которое меняет свой формат вывода, потому что оно обнаруживает канал в STDOUT, и я хочу, чтобы оно считало, что это интерактивный терминал, поэтому я получаю тот же вывод при перенаправлении.

Я думал, что обернуть его в expectсценарий или использовать proc_open()в PHP, но это не так.

Есть идеи?

жестяной человек
источник
8
Помогает ли empty.sf.net ?
ephemient
1
@ephemient: должен был быть ответ. Отличная утилита, кстати ...
Нейро
Вопрос говорит о stdout, но в названии упоминается stdin. Я думаю, что название неверно.
Петр Доброгост

Ответы:

177

Ага!

Команда scriptделает то, что мы хотим ...

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

Делает трюк!

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: просто наткнуться на проблему с библиотекой, которая выполняет статическую инициализацию. Недавнее изменение в Fedora 12 привело к сбою init, когда exe lanched не был в tty. Ваш трюк работает отлично. Я предпочел это, а не небуферы, так как скрипт установлен по умолчанию!
Нейро
scriptдаже доступен в BusyBox !
Дольмен
9
Если вы хотите передать это во что-то интерактивное, например less -R, куда идет ввод с терминала less -R, тогда вам понадобится дополнительная хитрость. Например, я хотел красочную версию git status | less. Вам нужно перейти -Rна less для того, чтобы он уважал цвета, и вам нужно использовать, scriptчтобы получить git statusвыходной цвет. Но мы не хотим scriptсохранять владение клавиатурой, мы хотим, чтобы это пошло less. Так что я использую это сейчас, и это работает хорошо 0<&- script -qfc "git status" /dev/null | less -R . Эти первые несколько символов закрывают стандартный ввод для этой команды.
Аарон МакДейд
2
Примечание: это не работает в случаях, когда компонент, проверяющий интерактивность, ищет $-переменную оболочки для «i».
Джей Тейлор
1
Это потрясающе. Я нуждался в этом для крайне редкого случая использования со встроенной библиотекой Python в исполняемом файле, который запускается в Wine. Когда я запускал в терминале, он работал, но когда я запускал файл .desktop, который я создал, он всегда падал, потому Py_Initializeчто не видел надлежащий stdin / stderr.
Татш
60

Основываясь на решении Криса , я предложил следующую маленькую вспомогательную функцию:

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

Причудливый вид printfнеобходим для правильного расширения аргументов скрипта $@при защите возможных частей команды, указанных в кавычках (см. Пример ниже).

Использование:

faketty <command> <args>

Пример:

$ 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
ingomueller.net
источник
9
Возможно, вы захотите использовать эту --returnопцию, если она есть в вашей версии script, чтобы сохранить код завершения дочернего процесса.
JWD
5
Я рекомендую изменить эту функцию следующим образом: в function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } противном случае файл с именем typescriptбудет создаваться при каждом запуске команды, во многих случаях.
W0rp
1
Кажется, не работает на MacOS, но я получаю script: illegal option -- f
Александр Миллс
23

Сценарий unbuffer, который поставляется с Expect, должен с этим справиться. Если нет, приложение может смотреть на что-то, кроме того, к чему его выход подключен, например. для чего установлена ​​переменная окружения TERM.

Колин Маклеод
источник
17

Я не знаю, выполнимо ли это из PHP, но если вам действительно нужен дочерний процесс для просмотра TTY, вы можете создать PTY .

В С:

#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 */
}

На самом деле у меня сложилось впечатление, что expectсам создает PTY.

ephemient
источник
Знаете ли вы, как запустить nettop как дочерний процесс на Mac OS X? Я хочу получить вывод неттопа в моем приложении. Я пытался использовать forkpty, но все еще не мог успешно запустить неттоп.
Винс Юань
16

Ссылаясь на предыдущий ответ, в Mac OS X можно использовать «скрипт», как показано ниже ...

script -q /dev/null commands...

Но, поскольку он может заменить "\ n" на "\ r \ n" на stdout, вам также может понадобиться такой скрипт:

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

Если между этими командами есть какой-то канал, вам нужно сбросить стандартный вывод. например:

script -q /dev/null commands... | ruby -ne 'print "....\n";STDOUT.flush' |  perl -pe 's/\r\n/\n/g'
Цунео Йошиока
источник
1
Спасибо за синтаксис OS X, но, судя по вашему утверждению Perl, кажется, вы хотели сказать, что он меняет экземпляры "\ r \ n" на "\ n", а не наоборот, правильно?
mklement0
8

Слишком новы, чтобы комментировать конкретный ответ, но я подумал, что буду следить за fakettyфункцией, опубликованной ingomueller-net выше, так как она недавно помогла мне.

Я обнаружил, что это создает typescriptфайл, который мне не нужен / не нужен, поэтому я добавил / dev / null в качестве целевого файла скрипта:

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

A-Рон
источник
3

Обновление ответа @ A-Ron на a) работу как в Linux, так и в MacOs b) косвенное распространение кода состояния (поскольку MacO scriptне поддерживает его)

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
}

Примеры:

$ 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

Я пытался получить цвета во время работы shellcheck <file> | less, поэтому я попробовал ответы выше, но они производят этот странный эффект, когда текст горизонтально смещен относительно того, где он должен быть:

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.

(Для тех, кто не знаком с shellcheck, строка с предупреждением должна совпадать с тем, где проблема.)

Для того, чтобы ответы выше работали с shellcheck, я попробовал один из вариантов из комментариев:

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

Это работает. Я также добавил --returnи использовал длинные опции, чтобы сделать эту команду чуть менее непостижимой:

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

Работает в Баш и Зш.

Ник Оделл
источник
1

В пример кода книги "Расширенное программирование в среде UNIX, второе издание" включена также pty-программа!

Вот как скомпилировать pty на 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 *
откровенный
источник
Действительно странная ошибка тоже: Быстрая ошибка: неизвестный домен: codesnippets.joyent.com. Пожалуйста, проверьте, что этот домен был добавлен к услуге.
i336_