Як обманути додаток на думку, що його stdout - це термінал, а не труба


147

Я намагаюся зробити протилежне " Виявити, якщо stdin - це термінал або труба? ".

Я запускаю програму, яка змінює свій вихідний формат, тому що вона виявляє трубку на STDOUT, і я хочу, щоб вона думала, що це інтерактивний термінал, щоб я отримав той самий вихід при перенаправлення.

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

Якісь ідеї там?


8
Чи допомагає empty.sf.net ?
ефемія

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 призвела до того, що ініціативу не вдалося, коли exe ланцюг був не в tty. Ваш трюк працює чудово. Я віддав перевагу це над розпарювачем, оскільки сценарій встановлений за замовчуванням!
нейро

scriptдоступний навіть у BusyBox !
долмен

9
Якщо ви хочете передати це в щось інтерактивне, наприклад less -R, куди йде термінальний вхід less -R, вам потрібна додаткова хитрість. Наприклад, я хотів барвисту версію git status | less. Вам потрібно перейти -Rдо менше, щоб воно поважало кольори, і вам потрібно скористатися, scriptщоб отримати git statusвихідний колір. Але ми не хочемо scriptзберігати право власності на клавіатуру, ми хочемо, щоб це перейшло less. Тому я використовую це зараз , і це добре працює: 0<&- script -qfc "git status" /dev/null | less -R . Ці перші кілька символів закривають stdin для цієї однієї команди.
Аарон Макдейд

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

9
Напевно, ви хочете скористатися --returnопцією, якщо scriptвона має вашу версію , для збереження коду виходу дочірнього процесу.
jwd

5
Я рекомендую змінити цю функцію так: function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } інакше файл з ім'ям typescriptстворюватиметься кожного разу, коли команда запускається, у багатьох випадках.
w0rp

1
не працює на MacOS, тому я розумію script: illegal option -- f
Олександр Міллз

23

Unbuffer скрипт , який поставляється з Очікувати повинен впоратися з цим ОК. Якщо ні, програма може дивитись на щось інше, ніж те, з чим підключений її вихід, наприклад. для чого встановлена ​​змінна середовище 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, хоча.


Чи знаєте ви, як запустити nettop як дочірній процес на mac os x? Я хочу отримати висновок неттопа у своєму додатку. Я спробував використовувати forkpty, але все ще не міг успішно запустити nettop.
Вінс Юань

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'

Якщо між цими командами є якась труба, вам потрібно промити stdout. наприклад:

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


3

Оновлення відповіді @ A-Ron на a) робота над Linux та MacOs b) поширювати код статусу опосередковано (оскільки MacOs 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.

(Для тих, хто не знає оболонки, рядок із попередженням повинен відповідати місцем, де проблема.)

Щоб відповіді вище працювали з шелчеком, я спробував один із варіантів коментарів:

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

Це працює. Я також додав --returnі використав довгі параметри, щоб зробити цю команду трохи менш непереборною:

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

Працює в Bash and Zsh.


1

Також у зразковому коді книги "Розширене програмування в середовищі UNIX, друге видання" є піт-програма!

Ось як компілювати 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 *

Дійсно дивна помилка теж: Швидко помилка: невідомий домен: codenippets.joyent.com. Перевірте, чи був доданий цей домен до послуги.
i336_
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.