Pipe in cmd-line text but then reset STDIN for menu


#1

Hello,
I’m trying to make a customized pager utility that can process
file-contents piped in from the command line, equally well as a list of
provided file names.
e.g. cat testfile | my_util.rb or my_util.rb testfile

I’m having trouble using the pipe to get the file contents and still be
able to use STDIN in a non-blocking way for a shell-style interactive
menu.

For the menu, I’m using Highline::SystemExtensions: e.g.:
while (mychar = get_character) && (mychar.chr != “q”)
if (mychar.chr == “x”) then
filter_all_but_selected
end
end

… and that works only for the case where there is no input pipe,
instead filename(s) I get from ARGV.

Here’s what I’ve tried to get the pipe read before begining the menu,
but thid hammers the rest of my script and my highline menu doesn’t
work:

def fill_pipe

capture piped input

flags = STDIN.fcntl(Fcntl::F_GETFL, 0)
flags |= Fcntl::O_NONBLOCK
STDIN.fcntl(Fcntl::F_SETFL, flags)
begin
input_pipe = STDIN.readlines
$working_pipe.concat(input_pipe)

puts “Input pipe: #{$input_pipe}”

rescue Errno::EAGAIN
puts “ERROR: no input piped nor file name provided”
display_help
end # end what kind of input
end # end fill_pipe

STDIN.close_read doesn’t do the trick to reset things – ideas how I
can have it both ways? Thanks! Andrew.


#2

Andrew Seidel wrote:

I’m having trouble using the pipe to get the file contents and still be
able to use STDIN in a non-blocking way for a shell-style interactive
menu.

I think what you’re asking is: how can I write a program which does the
same as this?

cat /usr/share/file/magic | less

(i.e. less reads its input from stdin, but can also read the terminal).

This is more a Unix question than a Ruby one. Probably the easiest way
to find out will be to read the source code of ‘less’.

The less manpage directs you to http://www.greenwoodsoftware.com/less/
from where you can download the source tarball.

The relevant bit:

    /*
     * Try /dev/tty.
     * If that doesn't work, use file descriptor 2,
     * which in Unix is usually attached to the screen,
     * but also usually lets you read from the keyboard.
     */

#if OS2
/* The __open() system call translates “/dev/tty” to “con”. */
tty = __open("/dev/tty", OPEN_READ);
#else
tty = open("/dev/tty", OPEN_READ);
#endif
if (tty < 0)
tty = 2;

That is: it opens /dev/tty for keyboard input, and falls back to fd 2 if
that fails. In Ruby that would be something like

mystdin = IO.open("/dev/tty") rescue IO.for_fd(2)

or if your user library can only talk to STDIN, then perhaps

STDIN.reopen("/dev/tty")

All untested.


#3

Brian,
Thanks! I’ll try some things out a let the forum know iff I produce
something of interest. Cheers, Andrew.