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

BashTerminalPipeStdin

Bash Problem Overview


I'm trying to do the opposite of "https://stackoverflow.com/questions/1312922/detect-if-stdin-is-a-terminal-or-pipe-in-c-c-qt";.

I'm running an application that's changing its output format because it detects a pipe on STDOUT, and I want it to think that it's an interactive terminal so that I get the same output when redirecting.

I was thinking that wrapping it in an expect script or using a proc_open() in PHP would do it, but it doesn't.

Any ideas out there?

Bash Solutions


Solution 1 - Bash

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

Solution 2 - Bash

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

Solution 3 - Bash

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.

Solution 4 - Bash

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'

Solution 5 - Bash

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.

Solution 6 - Bash

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

Solution 7 - Bash

Too new to comment on the specific answer, but I thought I'd followup on the faketty function posted by ingomueller-net above since it recently helped me out.

I found that this was creating a typescript file that I didn't want/need so I added /dev/null as the script target file:

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

Solution 8 - Bash

There's also a pty program included in the sample code of the book "Advanced Programming in the UNIX Environment, Second Edition"!

Here's how to compile pty on 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 *

Solution 9 - Bash

I was trying to get colors when running shellcheck <file> | less on Linux, so I tried the above answers, but they produce this bizarre effect where text is horizontally offset from where it should be:

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.

(For those unfamiliar with shellcheck, the line with the warning is supposed to line up with the where the problem is.)

In order to the answers above to work with shellcheck, I tried one of the options from the comments:

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

This works. I also added --return and used long options, to make this command a little less inscrutable:

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

Works in Bash and Zsh.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionChrisView Question on Stackoverflow
Solution 1 - BashChrisView Answer on Stackoverflow
Solution 2 - Bashingomueller.netView Answer on Stackoverflow
Solution 3 - BashColin MacleodView Answer on Stackoverflow
Solution 4 - BashTsuneo YoshiokaView Answer on Stackoverflow
Solution 5 - BashephemientView Answer on Stackoverflow
Solution 6 - BashJonas BerlinView Answer on Stackoverflow
Solution 7 - BashA-RonView Answer on Stackoverflow
Solution 8 - BashfrankView Answer on Stackoverflow
Solution 9 - BashNick ODellView Answer on Stackoverflow