Search code examples
rubylinuxunixttypty

How do I make stdin a tty?


There are programs which change their output depending on whether their stdout is a tty. So if you put these in a pipeline, or redirect them, the output is different than it would be in your shell. Here is an example:

$ touch a b c

# when running ls alone, it places them on one line
$ ls
a b c

# when stdout is not a tty, it places them on their own line
$ ls > output

$ cat output
a
b
c
output

So, if I want to show someone what a command like this should look like (e.g. I'm writing a tutorial), I have to highlight and copy the output from the terminal and then save it to a file.

It seems like I should be able to do something like this:

ls | ttypipe > output

Where ttypipe is a hypothetical program whose stdin (and hence ls's stdout) responds with true when asked if it's a tty.

I know that in Ruby, I can do something like this:

require 'pty'         # => true
IO.pipe.map(&:tty?)   # => [false, false]
PTY.open.map(&:tty?)  # => [true, true]

But that's for a child process, and not the current process, so as a result:

$stdin.tty?  # => false

I could exec, I can't think of a way to have that affect the stdin file descriptor.


Solution

  • You can't write ttypipe because a pipe is a pipe, and can never be a tty. However, you can write a ttyexec with slightly different syntax:

    ttyexec ls > output
    

    It would open a pseudo-terminal, run ls in it, and copy anything ls writes to the terminal to ttyexec's stdout.

    Lo and behold, there's a tool like this already: script. It opens a program in a new, hidden pty to log interactions with it, but we can ignore the logging part and just use its terminal opening properties:

    $ touch a b c
    
    $ ls
    a  b  c
    
    $ script -q -c 'ls' /dev/null > output
    
    $ cat output 
    a  b  c  output