{"id":905,"date":"2013-02-24T00:19:13","date_gmt":"2013-02-24T00:19:13","guid":{"rendered":"http:\/\/joelinoff.com\/blog\/?p=905"},"modified":"2013-02-26T21:04:45","modified_gmt":"2013-02-26T21:04:45","slug":"remote-command-execution-in-python-using-paramiko-that-supports-arbitrary-input","status":"publish","type":"post","link":"https:\/\/joelinoff.com\/blog\/?p=905","title":{"rendered":"Remote command execution in python using paramiko that supports arbitrary input"},"content":{"rendered":"<p>I recently decided to use paramiko to develop a remote command execution tool. <\/p>\n<p>It was very easy to setup initially and ran much faster than my existing pexpect implementation but it had a problem with sudo commands because they required the password to be provided as input. <\/p>\n<p>I solved the problem by using a pseudo-terminal and by creating my own ChannelFile objects for stdin and stdout\/stderr. The solution should be general enough to handle any case that requires simple input but it is not as flexible as pexpect. I hope that you find it useful.<br \/>\n<!--more--><\/p>\n<h1>Current Version<\/h1>\n<p>This is similar to the older version below but it handles more than 64KiB by eliminating the stdout buffer, the stdin buffer and using recv polling. It even does a silly check for input. That check could be expanded to make it behave more like pexpect but that is for another day.<br \/>\n[crayon lang=&#8221;python&#8221; toolbar=&#8221;always&#8221; title=&#8221;remote_exec&#8221;]<br \/>\n#!\/usr\/bin\/env python<br \/>\n&#8221;&#8217;<br \/>\nThis class allows you to run commands on a remote host and provide<br \/>\ninput if necessary.<\/p>\n<p>VERSION 1.2<br \/>\n&#8221;&#8217;<br \/>\nimport paramiko<br \/>\nimport logging<br \/>\nimport socket<br \/>\nimport time<br \/>\nimport datetime<\/p>\n<p># ================================================================<br \/>\n# class MySSH<br \/>\n# ================================================================<br \/>\nclass MySSH:<br \/>\n    &#8221;&#8217;<br \/>\n    Create an SSH connection to a server and execute commands.<br \/>\n    Here is a typical usage:<\/p>\n<p>        ssh = MySSH()<br \/>\n        ssh.connect(&#8216;host&#8217;, &#8216;user&#8217;, &#8216;password&#8217;, port=22)<br \/>\n        if ssh.connected() is False:<br \/>\n            sys.exit(&#8216;Connection failed&#8217;)<\/p>\n<p>        # Run a command that does not require input.<br \/>\n        status, output = ssh.run(&#8216;uname -a&#8217;)<br \/>\n        print &#8216;status = %d&#8217; % (status)<br \/>\n        print &#8216;output (%d):&#8217; % (len(output))<br \/>\n        print &#8216;%s&#8217; % (output)<\/p>\n<p>        # Run a command that does requires input.<br \/>\n        status, output = ssh.run(&#8216;sudo uname -a&#8217;, &#8216;sudo-password&#8217;)<br \/>\n        print &#8216;status = %d&#8217; % (status)<br \/>\n        print &#8216;output (%d):&#8217; % (len(output))<br \/>\n        print &#8216;%s&#8217; % (output)<br \/>\n    &#8221;&#8217;<br \/>\n    def __init__(self, compress=True, verbose=False):<br \/>\n        &#8221;&#8217;<br \/>\n        Setup the initial verbosity level and the logger.<\/p>\n<p>        @param compress  Enable\/disable compression.<br \/>\n        @param verbose   Enable\/disable verbose messages.<br \/>\n        &#8221;&#8217;<br \/>\n        self.ssh = None<br \/>\n        self.transport = None<br \/>\n        self.compress = compress<br \/>\n        self.bufsize = 65536<\/p>\n<p>        # Setup the logger<br \/>\n        self.logger = logging.getLogger(&#8216;MySSH&#8217;)<br \/>\n        self.set_verbosity(verbose)<\/p>\n<p>        fmt = &#8216;%(asctime)s MySSH:%(funcName)s:%(lineno)d %(message)s&#8217;<br \/>\n        format = logging.Formatter(fmt)<br \/>\n        handler = logging.StreamHandler()<br \/>\n        handler.setFormatter(format)<br \/>\n        self.logger.addHandler(handler)<br \/>\n        self.info = self.logger.info<\/p>\n<p>    def __del__(self):<br \/>\n        if self.transport is not None:<br \/>\n            self.transport.close()<br \/>\n            self.transport = None<\/p>\n<p>    def connect(self, hostname, username, password, port=22):<br \/>\n        &#8221;&#8217;<br \/>\n        Connect to the host.<\/p>\n<p>        @param hostname  The hostname.<br \/>\n        @param username  The username.<br \/>\n        @param password  The password.<br \/>\n        @param port      The port (default=22).<\/p>\n<p>        @returns True if the connection succeeded or false otherwise.<br \/>\n        &#8221;&#8217;<br \/>\n        self.info(&#8216;connecting %s@%s:%d&#8217; % (username, hostname, port))<br \/>\n        self.hostname = hostname<br \/>\n        self.username = username<br \/>\n        self.port = port<br \/>\n        self.ssh = paramiko.SSHClient()<br \/>\n        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())<br \/>\n        try:<br \/>\n            self.ssh.connect(hostname=hostname,<br \/>\n                             port=port,<br \/>\n                             username=username,<br \/>\n                             password=password)<br \/>\n            self.transport = self.ssh.get_transport()<br \/>\n            self.transport.use_compression(self.compress)<br \/>\n            self.info(&#8216;succeeded: %s@%s:%d&#8217; % (username,<br \/>\n                                               hostname,<br \/>\n                                               port))<br \/>\n        except socket.error as e:<br \/>\n            self.transport = None<br \/>\n            self.info(&#8216;failed: %s@%s:%d: %s&#8217; % (username,<br \/>\n                                                hostname,<br \/>\n                                                port,<br \/>\n                                                str(e)))<br \/>\n        except paramiko.BadAuthenticationType as e:<br \/>\n            self.transport = None<br \/>\n            self.info(&#8216;failed: %s@%s:%d: %s&#8217; % (username,<br \/>\n                                                hostname,<br \/>\n                                                port,<br \/>\n                                                str(e)))<\/p>\n<p>        return self.transport is not None<\/p>\n<p>    def run(self, cmd, input_data=None, timeout=10):<br \/>\n        &#8221;&#8217;<br \/>\n        Run a command with optional input data.<\/p>\n<p>        Here is an example that shows how to run commands with no input:<\/p>\n<p>            ssh = MySSH()<br \/>\n            ssh.connect(&#8216;host&#8217;, &#8216;user&#8217;, &#8216;password&#8217;)<br \/>\n            status, output = ssh.run(&#8216;uname -a&#8217;)<br \/>\n            status, output = ssh.run(&#8216;uptime&#8217;)<\/p>\n<p>        Here is an example that shows how to run commands that require input:<\/p>\n<p>            ssh = MySSH()<br \/>\n            ssh.connect(&#8216;host&#8217;, &#8216;user&#8217;, &#8216;password&#8217;)<br \/>\n            status, output = ssh.run(&#8216;sudo uname -a&#8217;, &#8216;<sudo-password>&#8216;)<\/p>\n<p>        @param cmd         The command to run.<br \/>\n        @param input_data  The input data (default is None).<br \/>\n        @param timeout     The timeout in seconds (default is 10 seconds).<br \/>\n        @returns The status and the output (stdout and stderr combined).<br \/>\n        &#8221;&#8217;<br \/>\n        self.info(&#8216;running command: (%d) %s&#8217; % (timeout, cmd))<\/p>\n<p>        if self.transport is None:<br \/>\n            self.info(&#8216;no connection to %s@%s:%s&#8217; % (str(self.username),<br \/>\n                                                     str(self.hostname),<br \/>\n                                                     str(self.port)))<br \/>\n            return -1, &#8216;ERROR: connection not established\\n&#8217;<\/p>\n<p>        # Fix the input data.<br \/>\n        input_data = self._run_fix_input_data(input_data)<\/p>\n<p>        # Initialize the session.<br \/>\n        self.info(&#8216;initializing the session&#8217;)<br \/>\n        session = self.transport.open_session()<br \/>\n        session.set_combine_stderr(True)<br \/>\n        session.get_pty()<br \/>\n        session.exec_command(cmd)<br \/>\n        output = self._run_poll(session, timeout, input_data)<br \/>\n        status = session.recv_exit_status()<br \/>\n        self.info(&#8216;output size %d&#8217; % (len(output)))<br \/>\n        self.info(&#8216;status %d&#8217; % (status))<br \/>\n        return status, output<\/p>\n<p>    def connected(self):<br \/>\n        &#8221;&#8217;<br \/>\n        Am I connected to a host?<\/p>\n<p>        @returns True if connected or false otherwise.<br \/>\n        &#8221;&#8217;<br \/>\n        return self.transport is not None<\/p>\n<p>    def set_verbosity(self, verbose):<br \/>\n        &#8221;&#8217;<br \/>\n        Turn verbose messages on or off.<\/p>\n<p>        @param verbose  Enable\/disable verbose messages.<br \/>\n        &#8221;&#8217;<br \/>\n        if verbose > 0:<br \/>\n            self.logger.setLevel(logging.INFO)<br \/>\n        else:<br \/>\n            self.logger.setLevel(logging.ERROR)<\/p>\n<p>    def _run_fix_input_data(self, input_data):<br \/>\n        &#8221;&#8217;<br \/>\n        Fix the input data supplied by the user for a command.<\/p>\n<p>        @param input_data  The input data (default is None).<br \/>\n        @returns the fixed input data.<br \/>\n        &#8221;&#8217;<br \/>\n        if input_data is not None:<br \/>\n            if len(input_data) > 0:<br \/>\n                if &#8216;\\\\n&#8217; in input_data:<br \/>\n                    # Convert \\n in the input into new lines.<br \/>\n                    lines = input_data.split(&#8216;\\\\n&#8217;)<br \/>\n                    input_data = &#8216;\\n&#8217;.join(lines)<br \/>\n            return input_data.split(&#8216;\\n&#8217;)<br \/>\n        return []<\/p>\n<p>    def _run_send_input(self, session, stdin, input_data):<br \/>\n        &#8221;&#8217;<br \/>\n        Send the input data.<\/p>\n<p>        @param session     The session.<br \/>\n        @param stdin       The stdin stream for the session.<br \/>\n        @param input_data  The input data (default is None).<br \/>\n        &#8221;&#8217;<br \/>\n        if input_data is not None:<br \/>\n            self.info(&#8216;session.exit_status_ready() %s&#8217; % str(session.exit_status_ready()))<br \/>\n            self.info(&#8216;stdin.channel.closed %s&#8217; % str(stdin.channel.closed))<br \/>\n            if stdin.channel.closed is False:<br \/>\n                self.info(&#8216;sending input data&#8217;)<br \/>\n                stdin.write(input_data)<\/p>\n<p>    def _run_poll(self, session, timeout, input_data):<br \/>\n        &#8221;&#8217;<br \/>\n        Poll until the command completes.<\/p>\n<p>        @param session     The session.<br \/>\n        @param timeout     The timeout in seconds.<br \/>\n        @param input_data  The input data.<br \/>\n        @returns the output<br \/>\n        &#8221;&#8217;<br \/>\n        interval = 0.1<br \/>\n        maxseconds = timeout<br \/>\n        maxcount = maxseconds \/ interval<\/p>\n<p>        # Poll until completion or timeout<br \/>\n        # Note that we cannot directly use the stdout file descriptor<br \/>\n        # because it stalls at 64K bytes (65536).<br \/>\n        input_idx = 0<br \/>\n        timeout_flag = False<br \/>\n        self.info(&#8216;polling (%d, %d)&#8217; % (maxseconds, maxcount))<br \/>\n        start = datetime.datetime.now()<br \/>\n        start_secs = time.mktime(start.timetuple())<br \/>\n        output = &#8221;<br \/>\n        session.setblocking(0)<br \/>\n        while True:<br \/>\n            if session.recv_ready():<br \/>\n                data = session.recv(self.bufsize)<br \/>\n                output += data<br \/>\n                self.info(&#8216;read %d bytes, total %d&#8217; % (len(data), len(output)))<\/p>\n<p>                if session.send_ready():<br \/>\n                    # We received a potential prompt.<br \/>\n                    # In the future this could be made to work more like<br \/>\n                    # pexpect with pattern matching.<br \/>\n                    if input_idx < len(input_data):\n                        data = input_data[input_idx] + '\\n'\n                        input_idx += 1\n                        self.info('sending input data %d' % (len(data)))\n                        session.send(data)\n\n            self.info('session.exit_status_ready() = %s' % (str(session.exit_status_ready())))\n            if session.exit_status_ready():\n                break\n\n            # Timeout check\n            now = datetime.datetime.now()\n            now_secs = time.mktime(now.timetuple()) \n            et_secs = now_secs - start_secs\n            self.info('timeout check %d %d' % (et_secs, maxseconds))\n            if et_secs > maxseconds:<br \/>\n                self.info(&#8216;polling finished &#8211; timeout&#8217;)<br \/>\n                timeout_flag = True<br \/>\n                break<br \/>\n            time.sleep(0.200)<\/p>\n<p>        self.info(&#8216;polling loop ended&#8217;)<br \/>\n        if session.recv_ready():<br \/>\n            data = session.recv(self.bufsize)<br \/>\n            output += data<br \/>\n            self.info(&#8216;read %d bytes, total %d&#8217; % (len(data), len(output)))<\/p>\n<p>        self.info(&#8216;polling finished &#8211; %d output bytes&#8217; % (len(output)))<br \/>\n        if timeout_flag:<br \/>\n            self.info(&#8216;appending timeout message&#8217;)<br \/>\n            output += &#8216;\\nERROR: timeout after %d seconds\\n&#8217; % (timeout)<br \/>\n            session.close()<\/p>\n<p>        return output<\/p>\n<p># ================================================================<br \/>\n# MAIN<br \/>\n# ================================================================<br \/>\nif __name__ == &#8216;__main__&#8217;:<br \/>\n    import sys<\/p>\n<p>    # Access variables.<br \/>\n    hostname = &#8216;hostname&#8217;<br \/>\n    port = 22<br \/>\n    username = &#8216;username&#8217;<br \/>\n    password = &#8216;password&#8217;<br \/>\n    sudo_password = password  # assume that it is the same password<\/p>\n<p>    # Create the SSH connection<br \/>\n    ssh = MySSH()<br \/>\n    ssh.set_verbosity(False)<br \/>\n    ssh.connect(hostname=hostname,<br \/>\n                username=username,<br \/>\n                password=password,<br \/>\n                port=port)<br \/>\n    if ssh.connected() is False:<br \/>\n        print &#8216;ERROR: connection failed.&#8217;<br \/>\n        sys.exit(1)<\/p>\n<p>    def run_cmd(cmd, indata=None):<br \/>\n        &#8221;&#8217;<br \/>\n        Run a command with optional input.<\/p>\n<p>        @param cmd    The command to execute.<br \/>\n        @param indata The input data.<br \/>\n        @returns The command exit status and output.<br \/>\n                 Stdout and stderr are combined.<br \/>\n        &#8221;&#8217;<br \/>\n        print<br \/>\n        print &#8216;=&#8217; * 64<br \/>\n        print &#8216;command: %s&#8217; % (cmd)<br \/>\n        status, output = ssh.run(cmd, indata)<br \/>\n        print &#8216;status : %d&#8217; % (status)<br \/>\n        print &#8216;output : %d bytes&#8217; % (len(output))<br \/>\n        print &#8216;=&#8217; * 64<br \/>\n        print &#8216;%s&#8217; % (output)<\/p>\n<p>    run_cmd(&#8216;uname -a&#8217;)<br \/>\n    run_cmd(&#8216;sudo ls -ltrh \/var\/log | tail&#8217;, sudo_password)  # sudo command<br \/>\n[\/crayon]<\/p>\n<h1>Older Version<\/h1>\n<p>This version will not handle more than 64KiB of output. I am not sure why.<br \/>\n[crayon lang=&#8221;python&#8221; toolbar=&#8221;always&#8221; title=&#8221;old_remote_exec&#8221;]<br \/>\n#!\/usr\/bin\/env python<br \/>\n&#8221;&#8217;<br \/>\nThis class allows you to run commands on a remote host and provide<br \/>\ninput if necessary.<br \/>\n&#8221;&#8217;<br \/>\nimport paramiko<br \/>\nimport logging<br \/>\nimport socket<br \/>\nimport time<\/p>\n<p># ================================================================<br \/>\n# class MySSH<br \/>\n# ================================================================<br \/>\nclass MySSH:<br \/>\n    &#8221;&#8217;<br \/>\n    Create an SSH connection to a server and execute commands.<br \/>\n    Here is a typical usage:<\/p>\n<p>        ssh = MySSH()<br \/>\n        ssh.connect(&#8216;host&#8217;, &#8216;user&#8217;, &#8216;password&#8217;, port=22)<br \/>\n        if ssh.connected() is False:<br \/>\n            sys.exit(&#8216;Connection failed&#8217;)<\/p>\n<p>        # Run a command that does not require input.<br \/>\n        status, output = ssh.run(&#8216;uname -a&#8217;)<br \/>\n        print &#8216;status = %d&#8217; % (status)<br \/>\n        print &#8216;output (%d):&#8217; % (len(output))<br \/>\n        print &#8216;%s&#8217; % (output)<\/p>\n<p>        # Run a command that does requires input.<br \/>\n        status, output = ssh.run(&#8216;sudo uname -a&#8217;, &#8216;sudo-password&#8217;)<br \/>\n        print &#8216;status = %d&#8217; % (status)<br \/>\n        print &#8216;output (%d):&#8217; % (len(output))<br \/>\n        print &#8216;%s&#8217; % (output)<br \/>\n    &#8221;&#8217;<br \/>\n    def __init__(self, compress=True, verbose=False):<br \/>\n        &#8221;&#8217;<br \/>\n        Setup the initial verbosity level and the logger.<\/p>\n<p>        @param compress  Enable\/disable compression.<br \/>\n        @param verbose   Enable\/disable verbose messages.<br \/>\n        &#8221;&#8217;<br \/>\n        self.ssh = None<br \/>\n        self.transport = None<br \/>\n        self.compress = compress<\/p>\n<p>        # Setup the logger<br \/>\n        self.logger = logging.getLogger(&#8216;MySSH&#8217;)<br \/>\n        self.set_verbosity(verbose)<\/p>\n<p>        fmt = &#8216;%(asctime)s MySSH:%(funcName)s:%(lineno)d %(message)s&#8217;<br \/>\n        format = logging.Formatter(fmt)<br \/>\n        handler = logging.StreamHandler()<br \/>\n        handler.setFormatter(format)<br \/>\n        self.logger.addHandler(handler)<br \/>\n        self.info = self.logger.info<\/p>\n<p>    def __del__(self):<br \/>\n        if self.transport is not None:<br \/>\n            self.transport.close()<br \/>\n            self.transport = None<\/p>\n<p>    def connect(self, hostname, username, password, port=22):<br \/>\n        &#8221;&#8217;<br \/>\n        Connect to the host.<\/p>\n<p>        @param hostname  The hostname.<br \/>\n        @param username  The username.<br \/>\n        @param password  The password.<br \/>\n        @param port      The port (default=22).<\/p>\n<p>        @returns True if the connection succeeded or false otherwise.<br \/>\n        &#8221;&#8217;<br \/>\n        self.info(&#8216;connecting %s@%s:%d&#8217; % (username, hostname, port))<br \/>\n        self.hostname = hostname<br \/>\n        self.username = username<br \/>\n        self.port = port<br \/>\n        self.ssh = paramiko.SSHClient()<br \/>\n        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())<br \/>\n        try:<br \/>\n            self.ssh.connect(hostname=hostname,<br \/>\n                             port=port,<br \/>\n                             username=username,<br \/>\n                             password=password)<br \/>\n            self.transport = self.ssh.get_transport()<br \/>\n            self.transport.use_compression(self.compress)<br \/>\n            self.info(&#8216;succeeded: %s@%s:%d&#8217; % (username,<br \/>\n                                               hostname,<br \/>\n                                               port))<br \/>\n        except socket.error:<br \/>\n            self.transport = None<br \/>\n            self.info(&#8216;failed: %s@%s:%d: %s&#8217; % (username,<br \/>\n                                                hostname,<br \/>\n                                                port,<br \/>\n                                                str(e)))<br \/>\n        except paramiko.BadAuthenticationType as e:<br \/>\n            self.transport = None<br \/>\n            self.info(&#8216;failed: %s@%s:%d: %s&#8217; % (username,<br \/>\n                                                hostname,<br \/>\n                                                port,<br \/>\n                                                str(e)))<\/p>\n<p>        return self.transport is not None<\/p>\n<p>    def run(self, cmd, input_data=None, timeout=10):<br \/>\n        &#8221;&#8217;<br \/>\n        Run a command with optional input data.<\/p>\n<p>        Here is an example that shows how to run commands with no input:<\/p>\n<p>            ssh = MySSH()<br \/>\n            ssh.connect(&#8216;host&#8217;, &#8216;user&#8217;, &#8216;password&#8217;)<br \/>\n            status, output = ssh.run(&#8216;uname -a&#8217;)<br \/>\n            status, output = ssh.run(&#8216;uptime&#8217;)<\/p>\n<p>        Here is an example that shows how to run commands that require input:<\/p>\n<p>            ssh = MySSH()<br \/>\n            ssh.connect(&#8216;host&#8217;, &#8216;user&#8217;, &#8216;password&#8217;)<br \/>\n            status, output = ssh.run(&#8216;sudo uname -a&#8217;, &#8216;<sudo-password>&#8216;)<\/p>\n<p>        @param cmd         The command to run.<br \/>\n        @param input_data  The input data (default is None).<br \/>\n        @param timeout     The timeout in seconds (default is 10 seconds).<br \/>\n        @returns The status and the output (stdout and stderr combined).<br \/>\n        &#8221;&#8217;<br \/>\n        self.info(&#8216;running command: (%d) %s&#8217; % (timeout, cmd))<\/p>\n<p>        if self.transport is None:<br \/>\n            self.info(&#8216;no connection to %s@%s:%s&#8217; % (str(self.username),<br \/>\n                                                     str(self.hostname),<br \/>\n                                                     str(self.port)))<br \/>\n            return -1, &#8216;ERROR: connection not established\\n&#8217;<\/p>\n<p>        # Fix the input data.<br \/>\n        input_data = self._run_fix_input_data(input_data)<\/p>\n<p>        # Initialize the session.<br \/>\n        self.info(&#8216;initializing the session&#8217;)<br \/>\n        session = self.transport.open_session()<br \/>\n        session.set_combine_stderr(True)<br \/>\n        session.get_pty()<br \/>\n        session.exec_command(cmd)<br \/>\n        stdin = session.makefile(&#8216;wb&#8217;, -1)<br \/>\n        stdout = session.makefile(&#8216;rb&#8217;, -1)<\/p>\n<p>        self._run_send_input(stdout, stdin, input_data)<br \/>\n        output = self._run_poll(stdout, timeout, input_data)<br \/>\n        status = stdout.channel.recv_exit_status()<br \/>\n        self.info(&#8216;output size %d&#8217; % (len(output)))<br \/>\n        self.info(&#8216;status %d&#8217; % (status))<br \/>\n        return status, output<\/p>\n<p>    def connected(self):<br \/>\n        &#8221;&#8217;<br \/>\n        Am I connected to a host?<\/p>\n<p>        @returns True if connected or false otherwise.<br \/>\n        &#8221;&#8217;<br \/>\n        return self.transport is not None<\/p>\n<p>    def set_verbosity(self, verbose):<br \/>\n        &#8221;&#8217;<br \/>\n        Turn verbose messages on or off.<\/p>\n<p>        @param verbose  Enable\/disable verbose messages.<br \/>\n        &#8221;&#8217;<br \/>\n        if verbose is True:<br \/>\n            self.logger.setLevel(logging.INFO)<br \/>\n        else:<br \/>\n            self.logger.setLevel(logging.ERROR)<\/p>\n<p>    def _run_fix_input_data(self, input_data):<br \/>\n        &#8221;&#8217;<br \/>\n        Fix the input data supplied by the user for a command.<\/p>\n<p>        @param input_data  The input data (default is None).<br \/>\n        @returns the fixed input data.<br \/>\n        &#8221;&#8217;<br \/>\n        if input_data is not None:<br \/>\n            if len(input_data) > 0:<br \/>\n                if &#8216;\\\\n&#8217; in input_data:<br \/>\n                    # Convert \\n in the input into new lines.<br \/>\n                    lines = input_data.split(&#8216;\\\\n&#8217;)<br \/>\n                    input_data = &#8216;\\n&#8217;.join(lines)<br \/>\n                if input_data[-1] != &#8216;\\n&#8217;:<br \/>\n                    input_data += &#8216;\\n&#8217;<br \/>\n        return input_data<\/p>\n<p>    def _run_send_input(self, stdout, stdin, input_data):<br \/>\n        &#8221;&#8217;<br \/>\n        Send the input data.<\/p>\n<p>        @param stdout      The stdout stream for the session.<br \/>\n        @param stdin       The stdin stream for the session.<br \/>\n        @param input_data  The input data (default is None).<br \/>\n        &#8221;&#8217;<br \/>\n        if  stdout.channel.closed is False:<br \/>\n            if input_data is not None:<br \/>\n                self.info(&#8216;sending input data&#8217;)<br \/>\n                stdin.write(input_data)<\/p>\n<p>    def _run_poll(self, stdout, timeout, input_data):<br \/>\n        &#8221;&#8217;<br \/>\n        Poll until the command completes.<\/p>\n<p>        @param timeout     The timeout in seconds.<br \/>\n        @param input_data  The input data.<br \/>\n        @returns the output<br \/>\n        &#8221;&#8217;<br \/>\n        interval = 0.1<br \/>\n        maxseconds = timeout<br \/>\n        maxcount = maxseconds \/ interval<\/p>\n<p>        # Poll until completion or timeout<br \/>\n        self.info(&#8216;polling (%d, %d)&#8217; % (maxseconds, maxcount))<br \/>\n        count = 0<br \/>\n        while stdout.channel.closed is False and count <= maxcount:\n            count += 1\n            time.sleep(interval)\n\n        # Polling finished.\n        if stdout.channel.closed is False:\n            # Some sort of error occurred, assume a timeout.\n            self.info('polling finished - timeout')\n            stdout.channel.close()\n            output = stdout.read()\n            output += '\\nERROR: timeout after %d seconds\\n' % (timeout)\n        else:\n            output = stdout.read()\n            self.info('polling finished - %d output bytes' % (len(output)))\n\n        if input_data is not None:\n            # Strip out the input data.\n            output = output[len(input_data):]\n            self.info('stripped %d input bytes' % (len(input_data)))\n\n        return output\n\n\n# ================================================================\n# MAIN\n# ================================================================\nif __name__ == '__main__':\n    import sys\n\n    # Access variables.\n    hostname = 'hostname'\n    port = 22\n    username = 'username'\n    password = 'password'\n    sudo_password = password  # assume that it is the same password\n\n    # Create the SSH connection\n    ssh = MySSH()\n    ssh.set_verbosity(False)\n    ssh.connect(hostname=hostname,\n                username=username,\n                password=password,\n                port=port)\n    if ssh.connected() is False:\n        print 'ERROR: connection failed.'\n        sys.exit(1)\n\n    def run_cmd(cmd, indata=None):\n        '''\n        Run a command with optional input.\n\n        @param cmd    The command to execute.\n        @param indata The input data.\n        @returns The command exit status and output.\n                 Stdout and stderr are combined.\n        '''\n        print\n        print '=' * 64\n        print 'command: %s' % (cmd)\n        status, output = ssh.run(cmd, indata)\n        print 'status : %d' % (status)\n        print 'output : %d bytes' % (len(output))\n        print '=' * 64\n        print '%s' % (output)\n\n    run_cmd('uname -a')\n    run_cmd('sudo ls -ltrh \/var\/log | tail', sudo_password)  # sudo command\n[\/crayon]\n\n<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I recently decided to use paramiko to develop a remote command execution tool. It was very easy to setup initially and ran much faster than my existing pexpect implementation but it had a problem with sudo commands because they required the password to be provided as input. I solved the problem by using a pseudo-terminal &hellip; <a href=\"https:\/\/joelinoff.com\/blog\/?p=905\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Remote command execution in python using paramiko that supports arbitrary input<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0},"categories":[5,7,16],"tags":[],"_links":{"self":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/905"}],"collection":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=905"}],"version-history":[{"count":12,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/905\/revisions"}],"predecessor-version":[{"id":996,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=\/wp\/v2\/posts\/905\/revisions\/996"}],"wp:attachment":[{"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=905"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=905"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joelinoff.com\/blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=905"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}