I have a number of bash/bind tools that I've written to simplify my command-line existence, and have recently wanted to make one of these tools interactive. If I try to read from stdin in one of these scripts, execution locks up at the point of reading. My example here is in python, but I have seen the exact same behavior when the invoked script is written in ruby:
~> cat tmp.py
import sys
sys.stdout.write(">>>")
sys.stdout.flush()
foo = sys.stdin.readline()
print "foo: %s" % foo,
~> python tmp.py
>>>yodeling yoda
foo: yodeling yoda
So the script works. When I invoke it, I can give it input and it prints what I fed it.
~> bind -x '"\eh":"echo yodeling yoda"'
[output deleted]
~> [Alt-H]
yodeling yoda
bind
works as expected. The bound keystroke invokes the command. I use this stuff all the time, but until now, I've only invoked scripts that required no stdin reads.
Let's bind [Alt-H] to the script:
~> bind -x '"\eh":"python tmp.py"'
[output deleted]
Now we're configured to have the script read from stdin while invoked by the bound keystroke. Hitting [Alt-H] starts the script but nothing typed is echoed back. Even hitting [Crl-D] doesn't end it. The only way to get out is to hit [Crl-C], killing the process in the readline. (sys.stdin.read() suffers the same fate.)
~> [Alt-H]
>>>Traceback (most recent call last):
File "tmp.py", line 7, in <module>
foo = sys.stdin.readline()
KeyboardInterrupt
As I mentioned at the top, I see the same issue with ruby, so I know it's nothing to do with the language I'm using. (I've omitted the script.)
~> bind -x '"\eh":"ruby tmp.rb"'
[Output deleted]
~> [Alt-H]
>>>tmp.rb:3:in `gets': Interrupt
from tmp.rb:3
I've looked through the Bash Reference Manual entry on bind, and it says nothing about a restriction on input. Any thoughts?
EDIT:
If I cat /proc/[PID]/fd/0 while the process is stuck, I see the script's input being displayed. (Oddly enough, a fair number of characters - seemingly at random - fail to appear here. This symptom only appears after I've given a few hundred bytes of input.)
Found this, a description of how and when a terminal switches between cooked and raw modes. Calling stty cooked
and stty echo
at the beginning of prompting, then stty sane
or stty raw
afterward triggers a new cascade of problems; mostly relating to how bound characters are handled, but suffice it to say that it destroys most alt bindings (and more) until return has been hit a couple times.
In the end, the best answer proved to be cooking the tty and manually turning on echo, then reverting the tty settings back to where they were when I started:
def _get_terminal_settings():
proc = subprocess.Popen(['/bin/stty', '-g'], stdout=subprocess.PIPE)
settings = proc.communicate()[0]
os.system('stty cooked echo')
return settings
def _set_terminal_settings(settings):
os.system('stty %s' % settings)
...
...
settings = _get_terminal_settings()
user_input = sys.stdin.readline()
_set_terminal_settings(settings)
...
...
You should be able to do this in any language you choose.
If you're curious about why this insanity is required, I would encourage you to read the link I added (under EDIT), above. The article doesn't cover anywhere enough detail, but you'll at least understand more than I did when I started.