Skip to content

Instantly share code, notes, and snippets.

@garybernhardt
Last active October 9, 2016 16:09
Show Gist options
  • Save garybernhardt/d496c69bf9e7cc03ddbd to your computer and use it in GitHub Desktop.
Save garybernhardt/d496c69bf9e7cc03ddbd to your computer and use it in GitHub Desktop.
# This is a stripped-down example based on Selecta's TTY handling. We store the
# TTY state in `tty_state`, then go into an infinite loop. When the loop is
# terminated by a ^C, we try to restore the TTY state. It's important that this
# work, but it doesn't in some subtle situations, and I don't know why.
#
# Save this file as test.rb and run it via this command, where `stty` should
# successfully restore the TTY state:
# bash -c 'echo | ruby test.rb'
#
# Next, run it via this command, where `stty` should fail to restore the TTY
# state:
# bash -c 'echo $(echo | ruby test.rb)'
#
# Clearly, the command substitution matters, but I don't know why. Initially, I
# thought that the TTY file might be gone by the time the SIGINT is sent,
# meaning there would be nothing for `stty` to see. However, you can see that
# in both commands the TTY file is still there.
#
# Here's what I get when I run those two commands and ^C them (on OS X 10.10,
# but this problem was originally observed in Linux):
#
# $ bash -c 'echo | ruby test.rb'
# ^C
# Did stty succeed?: true
# Is /dev/tty still there?: true
#
# $ bash -c 'echo $(echo | ruby test.rb)'
# ^C
# stty: tcsetattr: Input/output error
#
# Did stty succeed?: false
# Is /dev/tty still there?: true
tty_state = `stty -f /dev/tty -g`
begin
loop do end
rescue Interrupt
# Swallow the exception
ensure
$stderr.puts `stty -f /dev/tty #{tty_state}`
$stderr.puts "Did stty succeed?: #{$?.success?}"
$stderr.puts "Is /dev/tty still there?: #{!open("/dev/tty", "w").closed?}"
end
@pushcx
Copy link

pushcx commented Mar 5, 2015

For anyone tinkering with this: with "stty (GNU coreutils) 8.23" I had to change the -f to -F in the two stty commands to reproduce.

@winks
Copy link

winks commented Mar 5, 2015

What do I get for breaking the "it should work" case?

$ bash -c 'echo | ruby test.rb'
stty: when specifying an output style, modes may not be set
^Cstty: standard input: Invalid argument

Did stty succeed?: false
Is /dev/tty still there?: true

$ bash -c 'echo $(echo | ruby test.rb)'
stty: when specifying an output style, modes may not be set
^C
stty: standard input: Invalid argument

Did stty succeed?: false
Is /dev/tty still there?: true

starting bash 4.1.5(1)-release via zsh 4.3.10 in tmux 1.3-2+squeeze1 on Debian Squeeze, connected via putty 0.64

@aresnick
Copy link

aresnick commented Mar 8, 2015

I don't have any solution or explanation, but I took your question as a chance to learn a bit about TTYs. In my fiddling, I found that this actually works with dash as opposed to bash and thought you might appreciate knowing, given that they handle SIGNIT differently (cf. this). Good luck!

@stephen-smith
Copy link

ttystate doesn't possibly have some character in it that are only metacharacters in bash, but not in dash, does it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment