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

Viewed 60398

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 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?

9 Answers

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

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

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.

Related