How to trick an application into thinking its stdout is a terminal, not a pipe

前端 未结 9 1467
梦如初夏
梦如初夏 2020-11-22 11:58

I\'m trying to do the opposite of \"Detect if stdin is a terminal or pipe?\".

I\'m running an application that\'s changing its output format because it detects a pip

相关标签:
9条回答
  • 2020-11-22 12:20

    Updating @A-Ron's answer to a) work on both Linux & MacOs b) propagate status code indirectly (since MacOs script does not support it)

    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
    }
    

    Examples:

    $ 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
    
    0 讨论(0)
  • 2020-11-22 12:28

    Referring previous answer, on Mac OS X, "script" can be used like below...

    script -q /dev/null commands...
    

    But, because it may replace "\n" with "\r\n" on the stdout, you may also need script like this:

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

    If there are some pipe between these commands, you need to flush stdout. for example:

    script -q /dev/null commands... | ruby -ne 'print "....\n";STDOUT.flush' |  perl -pe 's/\r\n/\n/g'
    
    0 讨论(0)
  • 2020-11-22 12:29

    I don't know if it's doable from PHP, but if you really need the child process to see a TTY, you can create a PTY.

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

    I was actually under the impression that expect itself does creates a PTY, though.

    0 讨论(0)
  • 2020-11-22 12:31

    Based on Chris' solution, I came up with the following little helper function:

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

    The quirky looking printf is necessary to correctly expand the script's arguments in $@ while protecting possibly quoted parts of the command (see example below).

    Usage:

    faketty <command> <args>
    

    Example:

    $ 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
    
    0 讨论(0)
  • 2020-11-22 12:32

    Aha!

    The script command does what we want...

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

    Does the 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
    
    0 讨论(0)
  • 2020-11-22 12:32

    The unbuffer script that comes with Expect should handle this ok. If not, the application may be looking at something other than what its output is connected to, eg. what the TERM environment variable is set to.

    0 讨论(0)
提交回复
热议问题