Wie man eine Anwendung dazu bringt, zu denken, dass ihr Standard ein Terminal ist, keine Pipe


147

Ich versuche das Gegenteil von " Erkennen, ob stdin ein Terminal oder eine Pipe ist? " Zu tun .

Ich führe eine Anwendung aus, die ihr Ausgabeformat ändert, weil sie eine Pipe in STDOUT erkennt, und ich möchte, dass sie denkt, dass es sich um ein interaktives Terminal handelt, damit ich bei der Umleitung dieselbe Ausgabe erhalte.

Ich dachte, dass es in ein expectSkript verpackt oder proc_open()in PHP verwendet werden würde, aber das tut es nicht.

Irgendwelche Ideen da draußen?



1
@ Ephemient: hätte eine Antwort sein sollen. Übrigens ein großartiger Gebrauch ...
Neuro

Die Frage spricht von stdout, aber der Titel erwähnt stdin. Ich denke der Titel ist falsch.
Piotr Dobrogost

Antworten:


177

Aha!

Der scriptBefehl macht was wir wollen ...

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

Macht den Trick!

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: Stolpern Sie einfach über das Problem mit einer Bibliothek, die eine statische Initialisierung durchführt. Eine kürzliche Änderung in Fedora 12 hat dazu geführt, dass dieser Init fehlgeschlagen ist, als die Exe-Lanched nicht in einem tty war. Dein Trick funktioniert perfekt. Ich habe es dem Entpuffer vorgezogen, da das Skript standardmäßig installiert ist!
Neuro

scriptist sogar in BusyBox verfügbar !
Dolmen

9
Wenn Sie es in etwas Interaktives leiten möchten, z. B. less -Rin die Terminaleingabe less -R, benötigen Sie zusätzliche Tricks. Zum Beispiel wollte ich eine farbenfrohe Version von git status | less. Sie müssen zu -Rweniger übergeben, damit die Farben berücksichtigt werden, und Sie müssen verwenden script, um git statuszur Ausgabe der Farbe zu gelangen . Aber wir wollen nicht scriptdas Eigentum an der Tastatur behalten, wir wollen, dass dies geschieht less. Also benutze ich das jetzt und es funktioniert gut : 0<&- script -qfc "git status" /dev/null | less -R . Diese ersten paar Zeichen schließen stdin für diesen einen Befehl.
Aaron McDaid

2
Hinweis: Dies funktioniert nicht in Fällen, in denen die auf Interaktivität prüfende Komponente die $-Shell-Variable nach einem "i" durchsucht.
Jay Taylor

1
Das ist großartig. Ich brauchte dies für einen äußerst seltenen Anwendungsfall mit einer eingebetteten Python-Bibliothek in einer ausführbaren Datei, die in Wine ausgeführt wird. Wenn ich in einem Terminal lief, funktionierte es, aber wenn ich die von mir erstellte .desktop-Datei Py_Initializeausführte, stürzte sie immer ab, weil nicht das richtige stdin / stderr angezeigt wurde.
Tatsh

60

Basierend auf Chris 'Lösung habe ich mir die folgende kleine Hilfsfunktion ausgedacht:

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

Das skurrile Aussehen printfist erforderlich, um die Argumente des Skripts korrekt zu erweitern und $@gleichzeitig möglicherweise zitierte Teile des Befehls zu schützen (siehe Beispiel unten).

Verwendung:

faketty <command> <args>

Beispiel:

$ 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
Sie möchten wahrscheinlich die --returnOption verwenden, wenn Ihre Version von sie scripthat, um den Exit-Code des untergeordneten Prozesses beizubehalten.
JWD

5
Ich empfehle, diese Funktion folgendermaßen zu ändern: function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } Andernfalls typescriptwird in vielen Fällen bei jeder Ausführung eines Befehls eine Datei mit dem Namen erstellt.
w0rp

1
scheint nicht unter MacOS zu funktionieren, aber ich verstehe script: illegal option -- f
Alexander Mills

23

Das mit Expect gelieferte Unpuffer- Skript sollte dies in Ordnung bringen. Wenn nicht, betrachtet die Anwendung möglicherweise etwas anderes als das, mit dem ihre Ausgabe verbunden ist, z. auf was die Umgebungsvariable TERM eingestellt ist.


17

Ich weiß nicht, ob es mit PHP machbar ist, aber wenn Sie den untergeordneten Prozess wirklich benötigen, um ein TTY zu sehen, können Sie ein PTY erstellen .

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

Ich hatte tatsächlich den Eindruck, dass expectselbst ein PTY entsteht.


Wissen Sie, wie man Nettop als untergeordneten Prozess auf Mac OS X ausführt? Ich möchte die Ausgabe von nettop in meiner App erhalten. Ich habe versucht, forkpty zu verwenden, konnte aber nettop immer noch nicht erfolgreich ausführen.
Vince Yuan

16

Unter Bezugnahme auf die vorherige Antwort kann unter Mac OS X "Skript" wie folgt verwendet werden ...

script -q /dev/null commands...

Da es jedoch "\ n" im stdout durch "\ r \ n" ersetzen kann, benötigen Sie möglicherweise auch ein Skript wie das folgende:

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

Wenn sich zwischen diesen Befehlen eine Pipe befindet, müssen Sie stdout leeren. beispielsweise:

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

1
Vielen Dank für die OS X-Syntax, aber Ihrer Perl-Aussage nach scheint es, dass Sie damit sagen wollten, dass Instanzen von "\ r \ n" in "\ n" geändert werden, nicht umgekehrt, richtig?
mklement0

8

Zu neu, um die spezifische Antwort zu kommentieren, aber ich dachte, ich würde die fakettyFunktion von ingomueller-net weiter oben verfolgen, da sie mir kürzlich geholfen hat.

Ich stellte fest, dass dies eine typescriptDatei erstellte, die ich nicht wollte / brauchte, also fügte ich / dev / null als Skript-Zieldatei hinzu:

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


3

Aktualisierung der Antwort von @ A-Ron auf a) Arbeiten unter Linux und MacOs b) Weitergabe des Statuscodes indirekt (da MacOs scriptihn nicht unterstützen)

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
}

Beispiele:

$ 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

Ich habe versucht, beim Laufen Farben zu erhalten shellcheck <file> | less, daher habe ich die obigen Antworten ausprobiert, aber sie erzeugen diesen bizarren Effekt, bei dem der Text horizontal von der Stelle versetzt ist, an der er sein sollte:

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.

(Für diejenigen, die mit Shellcheck nicht vertraut sind, sollte die Zeile mit der Warnung mit der Stelle übereinstimmen, an der das Problem liegt.)

Um die obigen Antworten für die Arbeit mit Shellcheck zu erhalten, habe ich eine der Optionen aus den Kommentaren ausprobiert:

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

Das funktioniert. Ich habe auch --returnlange Optionen hinzugefügt und verwendet, um diesen Befehl etwas weniger unergründlich zu machen:

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

Arbeitet in Bash und Zsh.


1

Der Beispielcode des Buches "Erweiterte Programmierung in der UNIX-Umgebung, zweite Ausgabe" enthält auch ein Pty-Programm!

So kompilieren Sie pty unter 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 *

Wirklich seltsamer Fehler auch: Schnell Fehler: unbekannte Domain: Codesnippets.joyent.com. Bitte überprüfen Sie, ob diese Domain einem Dienst hinzugefügt wurde.
i336_
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.