how to send tab-key to python subprocess's std

2019-03-03 03:34发布

问题:

Background: I have a Python subprocess that connects to a shell-like application, which uses the readline library to handle input, and that app has a TAB-complete routine for command input, just like bash. The child process is spawned, like so:

def get_cli_subprocess_handle():
    return subprocess.Popen(
                            '/bin/myshell',
                            shell=False,
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.STDOUT,
                            )

Everything works great, except tab-complete. Whenever my Python program passes the tab character, '\t' to the subprocess, I get 5 spaces in the STDIN, instead of triggering the readline library's tab-complete routine. :(

Question: What can I send to the subprocess's STDIN to trigger the child's tab-complete function? Maybe asked another way: How do I send the TAB key as opposed to the TAB character, if that is even possible?

Related but Unanswered and Derailed:

trigger tab completion for python batch process built around readline

回答1:

The shell like application is probably differentiating between a terminal being connected to stdin and a pipe being connected to it. Many Unix utilities do just that to optimise their buffering (line vs. block) and shell-like utilities are likely to disable command completion facilities on batch input (i.e. PIPE) to avoid unexpected results. Command completion is really an interactive feature which requires a terminal input.

Check out the pty module and try using a master/slave pair as the pipe for your subprocess.



回答2:

There really is no such thing as sending a tab key to a pipe. A pipe can only accept strings of bits, and if the tab character isn't doing it, there may not be a solution.

There is a project that does something similar called pexpect. Just looking at its interact() code, I'm not seeing anything obvious that makes it work and yours not. Given that, the most likely explanation is that pexpect actually does some work to make itself look like a pseudo-terminal. Perhaps you could incorporate its code for that?



回答3:

Based on isedev's answer, I modified my code as follows:

import os, pty

def get_cli_subprocess_handle():
    masterPTY, slaveTTY = pty.openpty()
    return masterPTY, slaveTTY, subprocess.Popen(
                                                 '/bin/myshell',
                                                 shell=False,
                                                 stdin=slaveTTY,
                                                 stdout=slaveTTY,
                                                 stderr=slaveTTY,
                                                 )

Using this returned tuple, I was able to perform select.select([masterPTY],[],[]) and os.read(masterPTY, 1024) as needed, and I wrote to the master-pty with a function that is very similar to a private method in the pty module source:

def write_all(masterPTY, data):
    """Successively write all of data into a file-descriptor."""
    while data:
        chars_written = os.write(masterPTY, data)
        data = data[chars_written:]
    return data

Thanks to all for the good solutions. Hope this example helps someone else. :)