Search code examples
pythonpipestdinioctltermios

"Inappropriate ioctl for device" from termios with pipes


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())

Solution

  • 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.