Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

seperate input and output stream #618

Open
thelittlebug opened this issue Jan 19, 2020 · 4 comments
Open

seperate input and output stream #618

thelittlebug opened this issue Jan 19, 2020 · 4 comments

Comments

@thelittlebug
Copy link

hi,

thank you for pexpect. not everyone wants to learn tcl :)

i faced this challenge:
if you want to write complex chatscripts for pppd or mock some tests where you are not the caller but you get called, there is the issue that you have 2 filedescriptors: sys.stdin and sys.stdout.

i have "hack-fixed" this issue for me but i think a good solution would be a fdspawn with 2 seperated filehandles.

or am i missing another simpler solution?

thx

@thelittlebug
Copy link
Author

just to be sure everyone gets the use case. this is my ugly fix with no deeper understanding how pexpect is working. i've modified mainly the sending and reading part and inject either sys.stdin or sys.stdout wheter im sending or want to receive:

class Spawner(spawn):
    # todo: ask the maintainer if there is a way to add this feature to: https://github.com/pexpect/pexpect
    def __init__(self, command, args=[], timeout=30, maxread=2000,
                 searchwindowsize=None, logfile=None, cwd=None, env=None,
                 ignore_sighup=False, echo=True, preexec_fn=None,
                 encoding=None, codec_errors='strict', dimensions=None,
                 use_poll=False):
        super(spawn, self).__init__(timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize,
                                    logfile=logfile, encoding=encoding, codec_errors=codec_errors)
        #self.STDIN_FILENO = pty.STDIN_FILENO
        #self.STDOUT_FILENO = pty.STDOUT_FILENO
        #self.STDERR_FILENO = pty.STDERR_FILENO
        #self.cwd = cwd
        #self.env = env
        #self.echo = echo
        #self.ignore_sighup = ignore_sighup
        self.__irix_hack = sys.platform.lower().startswith('irix')
        self.use_poll = use_poll

    def read_nonblocking(self, size=1, timeout=-1):
        self.child_fd = sys.stdin.fileno()
        self.closed = False
        if self.closed:
            raise ValueError('I/O operation on closed file.')

        if self.use_poll:
            def select(timeout):
                return poll_ignore_interrupts([self.child_fd], timeout)
        else:
            def select(timeout):
                return select_ignore_interrupts([self.child_fd], [], [], timeout)[0]

        # If there is data available to read right now, read as much as
        # we can. We do this to increase performance if there are a lot
        # of bytes to be read. This also avoids calling isalive() too
        # often. See also:
        # * https://github.com/pexpect/pexpect/pull/304
        # * http://trac.sagemath.org/ticket/10295
        if select(0):
            try:
                incoming = super(spawn, self).read_nonblocking(size)
            except EOF:
                # Maybe the child is dead: update some attributes in that case
                self.isalive()
                raise
            while len(incoming) < size and select(0):
                try:
                    incoming += super(spawn, self).read_nonblocking(size - len(incoming))
                except EOF:
                    # Maybe the child is dead: update some attributes in that case
                    self.isalive()
                    # Don't raise EOF, just return what we read so far.
                    return incoming
            return incoming

        if timeout == -1:
            timeout = self.timeout

        if not self.isalive():
            # The process is dead, but there may or may not be data
            # available to read. Note that some systems such as Solaris
            # do not give an EOF when the child dies. In fact, you can
            # still try to read from the child_fd -- it will block
            # forever or until TIMEOUT. For that reason, it's important
            # to do this check before calling select() with timeout.
            if select(0):
                return super(spawn, self).read_nonblocking(size)
            self.flag_eof = True
            raise EOF('End Of File (EOF). Braindead platform.')
        elif self.__irix_hack:
            # Irix takes a long time before it realizes a child was terminated.
            # Make sure that the timeout is at least 2 seconds.
            # FIXME So does this mean Irix systems are forced to always have
            # FIXME a 2 second delay when calling read_nonblocking? That sucks.
            if timeout is not None and timeout < 2:
                timeout = 2

        # Because of the select(0) check above, we know that no data
        # is available right now. But if a non-zero timeout is given
        # (possibly timeout=None), we call select() with a timeout.
        if (timeout != 0) and select(timeout):
            return super(spawn, self).read_nonblocking(size)

        if not self.isalive():
            # Some platforms, such as Irix, will claim that their
            # processes are alive; timeout on the select; and
            # then finally admit that they are not alive.
            self.flag_eof = True
            raise EOF('End of File (EOF). Very slow platform.')
        else:
            raise TIMEOUT('Timeout exceeded.')

    def send(self, s):
        self.child_fd = sys.stdout.fileno()
        if self.delaybeforesend is not None:
            time.sleep(self.delaybeforesend)

        s = self._coerce_send_string(s)
        self._log(s, 'send')

        b = self._encoder.encode(s, final=False)
        return os.write(self.child_fd, b)

    def isalive(self):
        return True

@vivintsmartvideo
Copy link

I would like this option for logging as well, specifically, I would like to log stdout only for spawned bash shells, otherwise my logs look like this:

eecchhoo ""hheelloo  wwoorrlldd!!""

hello world

which makes reading my logs unpleasant.

Is there an option I'm missing? Or is a pexpect code change required for this feature?

@vivintsmartvideo
Copy link

I found this on stackoverflow https://stackoverflow.com/questions/13590578/print-and-pexpect-logging

For my problem using p.logfile_send and p.logfile_read did the trick. However, I can't see them mentioned in the documentation.

@jeberger
Copy link

Note that you can greatly simplify your "ugly fix" by simply forwarding the read/write functions to the base class:

class Child (pexpect.spawn):
    def __init__ (self, timeout = 30, maxread = 2000,
                  searchwindowsize = None, logfile = None,
                  encoding = None, codec_errors = 'strict',
                  use_poll = False):
        super().__init__ (
            None,
            timeout = timeout, maxread = maxread, searchwindowsize = searchwindowsize,
            logfile = logfile, encoding = encoding, codec_errors = codec_errors,
            use_poll = use_poll)
        self.closed = False

    def read_nonblocking (self, size=1, timeout=-1):
        self.child_fd = sys.stdin.fileno()
        return super().read_nonblocking (size, timeout)

    def send (self, s):
        self.child_fd = sys.stdout.fileno()
        return super().send (s)

    def isalive (self):
        return True

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants