Comment tromper une application en lui faisant croire que sa sortie standard est un terminal, pas un tube

147

J'essaye de faire le contraire de " Détecter si stdin est un terminal ou un tuyau? ".

J'exécute une application qui change son format de sortie car elle détecte un tube sur STDOUT, et je veux qu'elle pense qu'il s'agit d'un terminal interactif afin que j'obtienne la même sortie lors de la redirection.

Je pensais que l'envelopper dans un expectscript ou utiliser un proc_open()en PHP le ferait, mais ce n'est pas le cas.

Des idées là-bas?

l'homme d'étain
la source
8
Vide.sf.net aide- t- il ?
éphémère
1
@ephemient: aurait dû être une réponse. Great util by the way ...
neuro
La question parle de stdout mais le titre mentionne stdin. Je pense que le titre est faux.
Piotr Dobrogost

Réponses:

177

Ah!

La scriptcommande fait ce que nous voulons ...

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

Fait le truc!

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
la source
1
+1: il suffit de trébucher sur le problème avec une bibliothèque qui effectue une initialisation statique. Un changement récent dans Fedora 12 a fait échouer l'initialisation lorsque l'exécutable n'était pas dans un tty. Votre astuce fonctionne parfaitement. Je l'ai préféré au débuffeur car le script est installé par défaut!
neuro
scriptest même disponible dans BusyBox !
dolmen
9
Si vous voulez le diriger vers quelque chose d'interactif, comme l' less -Rendroit où l'entrée du terminal va less -R, alors vous avez besoin d'une astuce supplémentaire. Par exemple, je voulais une version colorée de git status | less. Vous devez passer -Rà moins pour qu'il respecte les couleurs, et vous devez utiliser scriptpour obtenir git statusla couleur de sortie. Mais nous ne voulons scriptpas garder la propriété du clavier, nous voulons que cela aille less. Donc , j'utilise ce maintenant et il fonctionne bien: 0<&- script -qfc "git status" /dev/null | less -R . Ces premiers personnages ferment le stdin pour celui-ci.
Aaron McDaid
2
Remarque: Cela ne fonctionne pas dans les cas où le composant vérifiant l'interactivité recherche la $-variable shell pour un "i".
Jay Taylor
1
Ceci est incroyable. J'en avais besoin pour un cas d'utilisation extrêmement rare avec une bibliothèque Python intégrée dans un exécutable exécuté dans Wine. Quand je courais dans un terminal, cela fonctionnait, mais quand j'exécutais le fichier .desktop que je créais, il plantait toujours parce Py_Initializeque je ne voyais pas correctement stdin / stderr.
Tatsh
60

Sur la base de la solution de Chris , j'ai proposé la petite fonction d'aide suivante:

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

L'aspect original printfest nécessaire pour développer correctement les arguments du script $@tout en protégeant les parties éventuellement entre guillemets de la commande (voir l'exemple ci-dessous).

Usage:

faketty <command> <args>

Exemple:

$ 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
la source
9
Vous souhaiterez probablement utiliser l' --returnoption, si votre version de l' scripta, pour conserver le code de sortie du processus enfant.
jwd
5
Je recommande de changer cette fonction comme ceci: function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } Sinon, un fichier nommé typescriptsera créé à chaque fois qu'une commande est exécutée, dans de nombreux cas.
w0rp
1
ne semble pas fonctionner sur MacOS mais je reçois script: illegal option -- f
Alexander Mills
23

Le script de suppression de tampon fourni avec Expect devrait gérer cela correctement. Sinon, l'application peut regarder autre chose que ce à quoi sa sortie est connectée, par exemple. la valeur de la variable d'environnement TERM.

Colin Macleod
la source
17

Je ne sais pas si c'est faisable à partir de PHP, mais si vous avez vraiment besoin du processus enfant pour voir un TTY, vous pouvez créer un PTY .

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

J'avais en fait l'impression que expectlui - même crée un PTY, cependant.

éphémère
la source
Savez-vous comment exécuter nettop en tant que processus enfant sur mac os x? Je veux obtenir la sortie de nettop dans mon application. J'ai essayé d'utiliser forkpty mais je n'ai toujours pas pu exécuter nettop avec succès.
Vince Yuan
16

En référence à la réponse précédente, sous Mac OS X, "script" peut être utilisé comme ci-dessous ...

script -q /dev/null commands...

Mais, comme il peut remplacer "\ n" par "\ r \ n" sur la sortie standard, vous pouvez également avoir besoin d'un script comme celui-ci:

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

S'il y a un tube entre ces commandes, vous devez vider stdout. par exemple:

script -q /dev/null commands... | ruby -ne 'print "....\n";STDOUT.flush' |  perl -pe 's/\r\n/\n/g'
Tsuneo Yoshioka
la source
1
Merci pour la syntaxe OS X, mais, à en juger par votre déclaration Perl, il semble que vous vouliez dire que cela change les instances de "\ r \ n" en "\ n", pas l'inverse, n'est-ce pas?
mklement0
8

Trop nouveau pour commenter la réponse spécifique, mais je pensais faire un suivi sur la fakettyfonction publiée par ingomueller-net ci-dessus car elle m'a récemment aidé.

J'ai trouvé que cela créait un typescriptfichier que je ne voulais pas / dont je n'avais pas besoin, j'ai donc ajouté / dev / null comme fichier cible du script:

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

A-Ron
la source
3

Mettre à jour la réponse de @ A-Ron à a) travailler à la fois sur Linux et MacOs b) propager le code d'état indirectement (puisque MacOs scriptne le prend pas en charge)

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
}

Exemples:

$ 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
la source
2

J'essayais d'obtenir des couleurs lors de l'exécution shellcheck <file> | less, j'ai donc essayé les réponses ci-dessus, mais elles produisent cet effet bizarre où le texte est décalé horizontalement de l'endroit où il devrait être:

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.

(Pour ceux qui ne connaissent pas shellcheck, la ligne avec l'avertissement est censée s'aligner avec l'endroit où se trouve le problème.)

Pour que les réponses ci-dessus fonctionnent avec shellcheck, j'ai essayé l'une des options des commentaires:

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

Cela marche. J'ai également ajouté --returnet utilisé des options longues, pour rendre cette commande un peu moins impénétrable:

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

Fonctionne dans Bash et Zsh.

Nick ODell
la source
1

Il y a aussi un programme pty inclus dans l'exemple de code du livre "Programmation avancée dans l'environnement UNIX, deuxième édition"!

Voici comment compiler pty sur 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 *
franc
la source
Erreur vraiment étrange aussi: Erreur rapide: domaine inconnu: codesnippets.joyent.com. Veuillez vérifier que ce domaine a été ajouté à un service.
i336_