Jak oszukać aplikację, by myślała, że ​​jej standardowe wyjście to terminal, a nie potok

147

Próbuję wykonać odwrotność od „ Wykryć, czy stdin to terminal czy potok? ”.

Uruchamiam aplikację, która zmienia swój format wyjściowy, ponieważ wykrywa potok na STDOUT i chcę, aby myślał, że jest to terminal interaktywny, aby podczas przekierowywania otrzymywał te same dane wyjściowe.

Myślałem, że owinięcie tego w expectskrypt lub użycie proc_open()w PHP by to zrobiło, ale tak nie jest.

Jakieś pomysły?

Blaszany Człowiek
źródło
8
Czy empty.sf.net pomaga?
ephemient
1
@ephemient: powinno być odpowiedzią. Nawiasem mówiąc, świetny użytek ...
neuro-
Pytanie dotyczy standardowego wyjścia, ale tytuł wspomina o stdin. Myślę, że tytuł jest zły.
Piotr Dobrogost

Odpowiedzi:

177

Aha!

scriptKomenda robi to, co chcemy ...

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

Zrób sztuczkę!

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
Christopher Oezbek
źródło
1
+1: po prostu napotkasz problem z biblioteką, która wykonuje statyczną inicjalizację. Niedawna zmiana w Fedorze 12 spowodowała, że ​​init zakończył się niepowodzeniem, gdy exe lanched nie był w tty. Twoja sztuczka działa idealnie. Wolałem to od unbuffera, ponieważ skrypt jest instalowany domyślnie!
neuro-
scriptjest nawet dostępny w BusyBox !
dolmen
9
Jeśli chcesz przesłać go do czegoś interaktywnego, na przykład miejsca less -R, do którego trafia wejście terminala less -R, potrzebujesz dodatkowej sztuczki. Na przykład chciałem mieć kolorową wersję git status | less. Musisz przejść -Rdo mniej, aby uszanować kolory, i musisz użyć, scriptaby uzyskać git statuskolor wyjściowy. Ale nie chcemy scriptzachować własności klawiatury, chcemy, żeby to się stało less. Więc używam to teraz i to działa dobrze: 0<&- script -qfc "git status" /dev/null | less -R . Tych kilka pierwszych znaków zamyka standardowe wejście dla tego jednego polecenia.
Aaron McDaid
2
Uwaga: to nie działa w przypadkach, gdy komponent sprawdzający interaktywność sprawdza $-zmienną powłoki „i”.
Jay Taylor
1
To jest niesamowite. Potrzebowałem tego w niezwykle rzadkim przypadku użycia z wbudowaną biblioteką Pythona w pliku wykonywalnym uruchamianym w Wine. Kiedy uruchomiłem terminal, zadziałało, ale kiedy uruchomiłem plik .desktop, który utworzyłem, zawsze zawieszał się, ponieważ Py_Initializenie widziałem prawidłowego stdin / stderr.
Tatsh
60

Opierając się na rozwiązaniu Chrisa , wymyśliłem następującą małą funkcję pomocniczą:

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

Dziwaczny wygląd printfjest konieczny, aby poprawnie rozwinąć argumenty skryptu, $@jednocześnie chroniąc ewentualnie cytowane części polecenia (patrz przykład poniżej).

Stosowanie:

faketty <command> <args>

Przykład:

$ 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
źródło
9
Prawdopodobnie będziesz chciał użyć tej --returnopcji, jeśli twoja wersja scriptma ją, aby zachować kod zakończenia procesu potomnego.
jwd
5
Zalecam zmianę tej funkcji w następujący sposób: function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } W przeciwnym razie plik o nazwie typescriptzostanie utworzony za każdym razem, gdy zostanie uruchomione polecenie, w wielu przypadkach.
w0rp
1
wygląda na to, że nie działa na MacOS, ale dostaję script: illegal option -- f
Alexander Mills
23

Unbuffer skrypt, który pochodzi z Oczekiwać należy obsługiwać ten przycisk OK. Jeśli nie, aplikacja może patrzeć na coś innego niż to, do czego jest podłączone jej wyjście, np. jaka jest wartość zmiennej środowiskowej TERM.

Colin Macleod
źródło
17

Nie wiem, czy jest to wykonalne z PHP, ale jeśli naprawdę potrzebujesz procesu potomnego, aby zobaczyć TTY, możesz utworzyć PTY .

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

Właściwie miałem wrażenie, że expectsam tworzy PTY.

ephemient
źródło
Czy wiesz, jak uruchomić nettop jako proces potomny w systemie Mac OS X? Chcę uzyskać dane wyjściowe nettopa w mojej aplikacji. Próbowałem użyć forkpty, ale nadal nie mogłem pomyślnie uruchomić nettopa.
Vince Yuan
16

Odwołując się do poprzedniej odpowiedzi, w systemie Mac OS X można użyć „skryptu” jak poniżej ...

script -q /dev/null commands...

Ale ponieważ może zamienić „\ n” na „\ r \ n” na standardowym wyjściu, możesz również potrzebować takiego skryptu:

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

Jeśli między tymi poleceniami znajduje się potok, musisz opróżnić stdout. na przykład:

script -q /dev/null commands... | ruby -ne 'print "....\n";STDOUT.flush' |  perl -pe 's/\r\n/\n/g'
Tsuneo Yoshioka
źródło
1
Dziękuję za składnię OS X, ale sądząc po wyrażeniu w Perlu, wydaje się, że chciałeś powiedzieć, że zmienia on wystąpienia „\ r \ n” na „\ n”, a nie odwrotnie, prawda?
mklement0
8

Zbyt nowy, by skomentować konkretną odpowiedź, ale pomyślałem, że podążę za fakettyfunkcją opublikowaną przez ingomueller-net powyżej, ponieważ ostatnio mi pomogła.

Odkryłem, że tworzyło to typescriptplik, którego nie chciałem / potrzebowałem, więc dodałem / dev / null jako plik docelowy skryptu:

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

A-Ron
źródło
3

Aktualizacja odpowiedzi @ A-Ron na a) działa na obu Linux i MacOs b) propaguje kod statusu pośrednio (ponieważ MacOs scriptgo nie obsługuje)

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
}

Przykłady:

$ 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
Jonas Berlin
źródło
2

Próbowałem uzyskać kolory podczas pracy shellcheck <file> | less, więc wypróbowałem powyższe odpowiedzi, ale dają one ten dziwny efekt, w którym tekst jest przesunięty w poziomie w stosunku do miejsca, w którym powinien być:

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.

(Dla tych, którzy nie są zaznajomieni z funkcją Shellcheck, wiersz z ostrzeżeniem powinien pokrywać się z miejscem, w którym występuje problem).

Aby powyższe odpowiedzi działały z shellcheck, wypróbowałem jedną z opcji z komentarzy:

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

To działa. Dodałem także --returni użyłem długich opcji, aby to polecenie było trochę mniej nieodgadnione:

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

Działa w Bash i Zsh.

Nick ODell
źródło
1

W przykładowym kodzie książki „Zaawansowane programowanie w środowisku UNIX, wydanie drugie” znajduje się również program pty!

Oto jak skompilować pty w systemie 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 *
szczery
źródło
Naprawdę dziwny błąd: Szybko błąd: nieznana domena: codenippets.joyent.com. Sprawdź, czy ta domena została dodana do usługi.
i336_