stramr上的Paramiko recv()/ read()/ readline(s)()返回空字符串

时间:2015-11-14 12:19:30

标签: python ssh paramiko stderr

我正在使用paramiko在远程主机上收集一些信息,并在read()频道中阅读(readline() / readlines() / stderr)时遇到问题。

有时stderr.read()会返回一个空字符串,这对我来说就像是竞争条件的结果。 但是,根据我在互联网上找到的文档和示例,这似乎是确切的方法。

我还尝试打开专用频道并使用chan.recv_ready() / chan.recv_stderr_ready()并通过chan.recv() / chan.recv_stderr()从循环中读取相应的频道同样的行为。

这是一个最小的测试用例 - 在我的设置中 - 可靠地导致该行为。

import paramiko

class SSH:
    def __init__(self):
        self.ssh = paramiko.SSHClient()
        self.ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.ssh.connect('127.0.0.1', port=31337, username='root', password='root')

        self.stdout = ''
        self.stderr = ''
        self.exit_code = 0

    def _run_cmd(self, cmd):
        self.stdout = ''
        self.stderr = ''

        stdin, stdout, stderr = self.ssh.exec_command(cmd)

        self.stdout = stdout.read()
        self.stderr = stderr.read()

        while not stdout.channel.exit_status_ready():
            pass
        self.exit_code = stdout.channel.recv_exit_status()

        if self.exit_code:
            print("ERROR: " + self.stderr)

    def process_list(self):
        self._run_cmd('ls /proc/ | grep -E "^[0-9]+$" | grep -v $$')
        lines = self.stdout.split('\n')[:-1]
        data = []

        for process in lines:
            process_data = {}
            process_data['pid'] = int(process)

            # fetching and parsing process status information from /proc/[PID]/status

            self._run_cmd('cat /proc/%d/status' % (int(process)))

            data.append(self.stdout)

        return data


data = SSH()
while True:
    print data.process_list()

经过几次运行(如果不是第一次),我得到的是: ERROR: 虽然我期待: ERROR: cat: /proc/12883/status: No such file or directory

如何确保stderr准备好读取/我读取stderr上的所有数据?

1 个答案:

答案 0 :(得分:0)

长话短说:我遇到了大部分问题,并为我的大部分ssh相关问题提出了最终解决方案。因此,请随时查看exec_command exec_command()的实现,避免大多数empty_response / stoping场景。

长篇故事

这里的主要问题是你exit_status是非阻塞的,并产生一个负责频道通信的线程。此线程正在等待传入数据并将其放入通道缓冲区,而主线程可能会继续。这就是你的问题所在。即使在您检查到您的身边已收到exit_status之前,您也要过早地阅读缓冲区。接收status_code确认远程进程已退出给定的状态代码。接收远程状态代码表示您已收到可能仍在传输的所有数据。它甚至可能在您身边无序到达,并表示即使在收到所有数据(stderrstdout)之前,stdout也可能会到达。

当数据到达时,主线程继续。在您的情况下,主线程尝试从stderrexit_status读取一次然后阻塞,直到exit_status准备就绪。请注意,通道线程仍可能从远程命令调用接收数据。另请注意,一旦您收到远程execute_command,您必须手动清空缓冲区。

在您的具体情况下,会发生以下情况:

  1. stdout产生一个新线程(让我们调用id exec_thread ,管理远程命令调用
  2. 虽然最近生成的线程可能会收到数据,但主线程会继续。
  3. [主要帖子]您在stderrself.stdout中存储self.stderrstderr个缓冲区的当前内容。请注意,您不知道自己是否已收到stdoutstdout的所有数据。很可能你刚收到一些块。
  4. [exec_thread]在[主线程]继续时,愉快地收到stderrexit_status的数据。
  5. [main-thread] busy-blocks直到收到stderr。请注意,此时您的缓冲区可能仍然已满,最近已收到stdoutexit_status块。
  6. [main-thread] self.exit_code存储在run_cmd中。 [exec_thread]仍然存在,可能仍会收到一些乱序数据。输入缓冲区可能仍会被填充。
  7. [main-thread]从exec_command
  8. 返回

    请注意,该频道的[exec_thread] - paramiko为每个频道调用创建一个主题(即stdout.channel.close()) - 仍处于活动状态,并且他们会总结空闲并创建问题,直到您手动关闭它们( INSERT INTO Tbl_Basicinfo(Employer_Id,Lname,Fname,UserName,Userpass,Location_Id,cc2,CC3,cc4,cc5,Dohire,Job_Status,ssn,Import_Emp_No,[Benefit_Terminate], [Show_Age], [Change_IP], [IsNewHired]) SELECT M.Employer_Id,M.LASTNAME,M.FIRSTNAME,UPPER(LEFT(ISNULL(M.FIRSTNAME,'A'),1)+''+LEFT(ISNULL(M.LASTNAME,'C'),1))+CONVERT(VARCHAR,ISNULL(MaxImportEENumber,0)+ISNULL(ROWNUM,0)) [UserName],RIGHT(NEWID(),10) [UserPass],ISNULL(M.Location_Id,0)[Location_Id],M.Level2code,M.Level3code,M.LEVEL4Code,M.LEVEL5Code,M.DATEOFHIRE, 1 [JobStatus],SSN,M.MaxImportEENumber+ROWNUM [ImportEmpNo],0 [Benefit_Terminate], 0[Show_Age],M.ChangeIP,1 [IsNewHired] from( SELECT ROW_NUMBER() OVER(ORDER BY [FIRSTNAME]) AS ROWNUM,* FROM ( SELECT DISTINCT @Employer_Id [Employer_Id],PR.LASTNAME,PR.FIRSTNAME,(SELECT MAX(CAST(ISNULL(Employee_Id,0) as BIGINT)) from Tbl_Basicinfo) [MaxImportEENumber], (SELECT TOP 1 Location_Id from Tbl_Location where Location_Code=PR.LEVEL1CODE and Employer_Id=@Employer_Id and Location like '%'+PR.LEVEL1DESCRIPTION+'%') [Location_Id], (SELECT TOp 1 LEVEL2Code from Tbl_PPACA_Import where ssn=PR.SSN and EmployerId=PR.EmployerId) LEVEL2Code, (SELECT TOp 1 LEVEL3Code from Tbl_PPACA_Import where ssn=PR.SSN and EmployerId=PR.EmployerId) LEVEL3Code, (SELECT TOp 1 LEVEL4Code from Tbl_PPACA_Import where ssn=PR.SSN and EmployerId=PR.EmployerId) LEVEL4Code, (SELECT TOp 1 LEVEL5Code from Tbl_PPACA_Import where ssn=PR.SSN and EmployerId=PR.EmployerId) LEVEL5Code, (SELECT MIN(DATEOFHIRE) from Tbl_PPACA_Import where ssn=PR.SSN and EmployerId=PR.EmployerId) DATEOFHIRE,1 Job_Status,PR.SSN,PR.ChangeIP from Tbl_PPACA_Import PR LEFT OUTER JOIN Tbl_Basicinfo L ON L.ssn=PR.SSN and L.Employer_Id=PR.EmployerId WHERE PR.EmployerId=@Employer_Id and L.SSN is null and ISNULL(PR.SSN,'') not like '' AND ISNUMERIC(PR.SSN)=1 and ISNULL(PR.PPE,DATEOFHIRE) =(SELECT MAX(ISNULL(PPE,DATEOFHIRE)) FROM Tbl_PPACA_Import I WHERE I.SSN=PR.SSN AND I.EmployerId=PR.EmployerId ) ) V ) M; )。