I was working on a project yesterday and I came across an issue that I haven't encountered before. I'm current using argparse to ask for an input filename, and I'm adding support for piping a file via stdin to my program. I've got it all set up and working, except for this termios issue I'm encountering whenever I pipe a file to my program, and I was wondering if anyone knows a solution. The exact error I'm getting is
old_settings = self.termios.tcgetattr(fd)
termios.error: (25, 'Inappropriate ioctl for device')
This specifically is coming from the getkey module, because I need something for non-blocking input (feel free to inform me of better options). I'm assuming it's happening because its standard I/O streams aren't connected to a terminal because of the pipe, but I don't know if there's a way I can actually fix that and I can't really find any solutions on stackoverflow or Google. Here's a minimal reproducible example:
# Assuming filename is test.py, running
# python3 test.py
# works, but running
# cat test.py | python3 test.py
# or
# python3 test.py < test.py
# results in an error
import sys
import termios
termios.tcgetattr(sys.stdin.fileno())
I figured out a solution using the pty
module which I didn't know about before. The example I gave can be fixed by using pty.fork()
to connect the child to a new pseudo-terminal. This program seemed to work:
import pty
import sys, termios
pid = pty.fork()
if not pid:
# is child
termios.tcgetattr(sys.stdin.fileno())
I also found a solution for if the error is coming from a new python process created with subprocess
. This version utilizes pty.openpty()
to create a new pseudo-terminal pair.
import subprocess, pty, sys
# Create new tty to handle ioctl errors in termios
master_fd, slave_fd = pty.openpty()
proc = subprocess.Popen([sys.executable, 'my_program.py'], stdin=slave_fd)
Hope this helps someone else out.