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

3 weeks ago 1

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

Christopher Oezbek's user avatar

answered Sep 9, 2009 at 22:06

16 Comments

+1: just stumble on the problem with a lib that do static initialisation. A recent change in Fedora 12 has make that init fail when the exe lanched was not in a tty. Your trick works perfectly. I preferred it over unbuffer as script is installed by default !

2010-11-26T11:23:54.883Z+00:00

If you want to pipe it into something interactive, like less -R, where terminal input goes to less -R, then you need some extra trickery. For example, I wanted a colourful version of git status | less. You need to pass -R to less in order that it respect the colours, and you need to use script to get git status to output colour. But we don't want script to keep ownership of the keyboard, we want this to go to less. So I use this now and it works well: 0<&- script -qfc "git status" /dev/null | less -R . Those first few characters close stdin for this one commmand.

2014-11-26T13:54:10.523Z+00:00

Note: This doesn't work in cases where the component checking for interactivity is looking at the $- shell variable for an "i".

2015-04-28T19:10:50.377Z+00:00

This is amazing. I needed this for an extremely rare use case with an embedded Python library within an executable that is run within Wine. When I ran in a terminal it worked but when I ran the .desktop file I created it would always crash because Py_Initialize didn't see proper stdin/stderr.

2018-09-23T08:50:44.13Z+00:00

Is there an OS agnostic way of doing this? I like the script command on macOS because you don't have to wrap the command in quotes. The script runs and sends output to the tty which is duplicated in the supplied file, but I can't seem to get the linux version to behave the same way... I'm probably doing something wrong. So what's the equivalent linux script command for this on macOS: script -q -t 0 tmp.out perl -e 'print "Test\n"' Test cat tmp.out Test

2018-10-23T14:45:02.747Z+00:00

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

a505999's user avatar

a505999

3805 silver badges11 bronze badges

answered Dec 5, 2013 at 13:47

ingomueller.net's user avatar

3 Comments

You probably want to use the --return option, if your version of script has it, to preserve the child process' exit code.

2015-06-08T17:30:14.383Z+00:00

I recommend changing this function like so: function faketty { script -qfc "$(printf "%q " "$@")" /dev/null; } Otherwise, a file named typescript will be created each time a command is run, in many cases.

2017-03-03T16:27:20.227Z+00:00

doesn't seem to work on MacOS tho, I get script: illegal option -- f

2019-05-16T21:24:17.23Z+00:00

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.

answered Sep 11, 2009 at 11:03

Colin Macleod's user avatar

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'

William Stanley's user avatar

answered Nov 27, 2012 at 15:51

Tsuneo Yoshioka's user avatar

1 Comment

Thanks for the OS X syntax, but, judging by your Perl statement, it seems that you meant to say that it changes instances of "\r\n" to "\n", not the other way around, correct?

2013-06-14T21:19:46.297Z+00:00

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.

TheLizzard's user avatar

TheLizzard

7,8002 gold badges16 silver badges43 bronze badges

answered Sep 9, 2009 at 19:21

ephemient's user avatar

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

CPPUIX's user avatar

CPPUIX

2,4098 gold badges17 silver badges38 bronze badges

answered Feb 18, 2020 at 11:04

Jonas Berlin's user avatar

3 Comments

I use a nonstandard SHELL so this function was failing in my (Linux) environment. The script command uses SHELL to run /bin/sh -c ... which runs $cmd. By explicitly setting SHELL=/bin/sh it seems possible to cut out one of those three stages. I replaced the Linux script line with the simpler SHELL=/bin/sh script -qfc "$cmd" /dev/null, and it appears to function identically.

2021-12-16T15:35:37.787Z+00:00

on Mac: Your script doesn't seem to take care of stdin, does it? E.g. uniq doesn't seem to work when it's wrapped in faketty while getting its stdin from a pipe: grep --line-buffered ... | faketty uniq. (I need faketty because I require to pipe uniq's output to yet another command without buffering.)

2023-11-17T06:45:44.743Z+00:00

Unfortunately I don't have a Mac (a friend helped me out to create the Mac part) and can't seem to find the manual page easily.

2024-01-15T17:46:29.28Z+00:00

A followup on the helpful faketty function mentioned in this answer.

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 ; }

CPPUIX's user avatar

CPPUIX

2,4098 gold badges17 silver badges38 bronze badges

answered Jan 29, 2017 at 8:39

A-Ron's user avatar

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 *

Nick ODell's user avatar

Nick ODell

27.8k7 gold badges51 silver badges93 bronze badges

answered Aug 5, 2010 at 10:40

frank's user avatar

1 Comment

Really weird error, too: Fastly error: unknown domain: codesnippets.joyent.com. Please check that this domain has been added to a service.

2016-11-16T12:36:13.053Z+00:00

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.

answered Aug 6, 2019 at 17:14

Nick ODell's user avatar

3 Comments

quite sure that does not work in neither bash nor zsh on bsd systems.

2021-11-05T20:50:32.453Z+00:00

@oligofren I encourage you to submit an answer that does, then.

2021-11-05T21:59:33.85Z+00:00

No need, another answer covers that. I was just saying that the main driver of your answer is OS dependant, regardless of the shell. I can make an edit instead, to clarify :)

2021-11-05T22:23:52.757Z+00:00

After failing to get the other approaches working (unbuffer, stdbuf -o0 -e0, script), I implemented a tiny forkpty wrapper along the lines of ephemient's answer, but handling signals, child output, and child exit status: github.com/cyanogilvie/forcetty. (unbuffer actually does work, but pulls in expect which can bring in a lot of dependencies. My little wrapper is just about 100 lines of plain c with no dependencies so it might work better in tiny containers and such).

answered Aug 27, 2024 at 18:16

Cyan Ogilvie's user avatar

Read Entire Article