Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1782390
  • 博文数量: 297
  • 博客积分: 285
  • 博客等级: 二等列兵
  • 技术积分: 3006
  • 用 户 组: 普通用户
  • 注册时间: 2010-03-06 22:04
个人简介

Linuxer, ex IBMer. GNU https://hmchzb19.github.io/

文章分类

全部博文(297)

文章存档

2020年(11)

2019年(15)

2018年(43)

2017年(79)

2016年(79)

2015年(58)

2014年(1)

2013年(8)

2012年(3)

分类: Python/Ruby

2016-11-14 22:20:48

最近看了点paramiko 的一段代码,本来想把这段代码用select 改下的。所以做了以下实验
原始代码如下:我本来想把那两段recv(512) 然后判断长度的代码改成用select 实现。

点击(此处)折叠或打开

  1. ssh = paramiko.SSHClient()
  2. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  3. ssh.connect('127.0.0.1',username='root',password='passw0rd')

  4. output=""
  5. err_str=""

  6. cli_channel=ssh.get_transport().open_channel('session')
  7. cli_channel.settimeout(600)
  8. #default timeout 3600 seconds
  9. cli_channel.exec_command("/usr/bin/cp /root/names.txt /tmp/names.txt.before")
  10. #cli_channel.exec_command("/usr/bin/cp /root/names.txt /tmp/names.txt.before; echo good bye; sleep 1; ls -al /dev; grep -Ev '^[[:space:]]*#' /usr/local/src/py/network/paramiko_2.py")
  11. try:
  12.     out=cli_channel.recv(512)
  13.     while len(out) > 0:
  14.         output+=out.decode()
  15.         out=cli_channel.recv(512)

  16.     out=cli_channel.recv_stderr(512)
  17.     while len(out) > 0:
  18.         err_str+=out.decode()
  19.         out=cli_channel.recv_stderr(512)

  20. except socket.timeout:
  21.     print("Operation time out. Ouput might not be complete.")
  22. rc=cli_channel.recv_exit_status()
  23. print(rc)
  24. print(output)
  25. print(err_str)
  26. print ("Command done, closing SSH connection")
  27. ssh.close()
其实最tricky的地方就在于我用的这两段shell。 
/usr/bin/cp /root/names.txt /tmp/names.txt.before: 这一段cp的路径应该是/bin/cp,应该报ret code 127,  bash: /usr/bin/cp: No such file or directory
/usr/bin/cp /root/names.txt /tmp/names.txt.before; echo good bye; sleep 1; ls -al /dev; grep -Ev '^[[:space:]]*#' /usr/local/src/py/network/paramiko_2.py: 
这个更加复杂,执行了第一个命令报错,但是因为冒号";", return code会被丢弃,继续向后面执行,最后还有个 grep -Ev 这里面我并不知道会不会出现特殊字符替换什么的。但是我发现上述代码执行的没有问题。

改写的第一个select 版本。

点击(此处)折叠或打开

  1. ssh = paramiko.SSHClient()
  2. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  3. ssh.connect('127.0.0.1',username='root',password='passw0rd')

  4. output=""
  5. err_str=""

  6. cli_channel=ssh.get_transport().open_channel('session')
  7. cli_channel.settimeout(3600)
  8. stdout = cli_channel.exec_command("/usr/bin/cp /root/names.txt /tmp/names.txt.before")

  9. #output=cli_channel.recv(512).decode()
  10. #err_str=cli_channel.recv_stderr(512).decode()
  11. try:
  12.     while not cli_channel.exit_status_ready():
  13.         rl, wl, xl = select.select([cli_channel], [], [], 0.0)
  14.         if len(rl) > 0:
  15.             output+=cli_channel.recv(512).decode()
  16.             err_str+=cli_channel.recv_stderr(512).decode()
  17. except socket.timeout:
  18.     print("Operation time out. Ouput might not be complete.")

  19. rc=cli_channel.recv_exit_status()
  20. print(rc)
  21. print(output)
  22. print(err_str)

  23. print ("Command done, closing SSH connection")
  24. ssh.close()
请注意我注释掉掉的两行: 当这两行被注释掉时运行的结果让我大跌眼镜。return code没有问题,但是stderr 哪里去了? 有时候有,有时候没有。只有这两行都在的情况下,才能正确的打印出stderr.
当然这是因为我是在对loopback 做ssh的操作,实际上很少有人这么干吧。

点击(此处)折叠或打开

  1. root@kali:/usr/local/src/py/network# ./paramiko_2_exp1.py 
  2. 127

  3. bash: /usr/bin/cp: No such file or directory

  4. Command done, closing SSH connection
  5. root@kali:/usr/local/src/py/network# ./paramiko_2_exp1.py 
  6. 127


  7. Command done, closing SSH connection
下面是稍微修改下的版本。

点击(此处)折叠或打开

  1. ssh = paramiko.SSHClient()
  2. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  3. ssh.connect('127.0.0.1',username='root',password='passw0rd')

  4. output=""
  5. err_str=""

  6. cli_channel=ssh.get_transport().open_channel('session')
  7. cli_channel.settimeout(3600)
  8. #stdout = cli_channel.exec_command("/usr/bin/cp /root/names.txt /tmp/names.txt.before")
  9. cli_channel.exec_command("/usr/bin/cp /root/names.txt /tmp/names.txt.before; echo yes; sleep 1; echo good bye; sleep 1; ls -al /dev")

  10. output=cli_channel.recv(512).decode()
  11. err_str=cli_channel.recv_stderr(512).decode()
  12. try:
  13.     while not cli_channel.exit_status_ready():
  14.         rl, wl, xl = select.select([cli_channel], [], [], 0.0)
  15.         if len(rl) > 0:
  16.             output+=cli_channel.recv(512).decode()
  17.             err_str+=cli_channel.recv_stderr(512).decode()
  18. except socket.timeout:
  19.     print("Operation time out. Ouput might not be complete.")

  20. rc=cli_channel.recv_exit_status()
  21. print(rc)
  22. print(output)
  23. print(err_str)

  24. print ("Command done, closing SSH connection")
  25. ssh.close()
执行结果如下:你永远不知道分号这个东西";"能带来多少不确定性。而且 ls -al /dev根本没有任何输出。

点击(此处)折叠或打开

  1. root@kali:/usr/local/src/py/network# ./paramiko_2_exp2.py 
  2. 0
  3. yes
  4. good bye

  5. bash: /usr/bin/cp: No such file or directory

  6. Command done, closing SSH connection
  7. root@kali:/usr/local/src/py/network# ./paramiko_2_exp2.py 
  8. 0
  9. yes
  10. good bye

  11. bash: /usr/bin/cp: No such file or directory

  12. Command done, closing SSH connection
所以这么看,我在使用channel对象的exec_command()的时候,应该一次执行一个命令,而不是尝试多个shell命令用";"连在一起执行。当然我没有尝试&& 和 || . 

最后两个还是老实一点吧: 不使用select 的例子如下

点击(此处)折叠或打开
   ssh = paramiko.SSHClient()


  1. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  2. ssh.connect('127.0.0.1',username='root',password='passw0rd')
  3. output=""
  4. err_str=""

  5. cli_channel=ssh.get_transport().open_channel('session')
  6. cli_channel.settimeout(600)


  7. try:
  8.     out=cli_channel.recv(512)
  9.     while len(out) > 0:
  10.         output+=out.decode()
  11.         out=cli_channel.recv(512)

  12.     out=cli_channel.recv_stderr(512)
  13.     while len(out) > 0:
  14.         err_str+=out.decode()
  15.         out=cli_channel.recv_stderr(512)

  16. except socket.timeout:
  17.         print("Operation time out. Ouput might not be complete.")
  18. rc=cli_channel.recv_exit_status()
  19. print(rc)
  20. print(output)
  21. print(err_str)

  22. print ("Command done, closing SSH connection")
  23. ssh.close()

最后的一个例子,使用了select, 传入了get_pty=True

点击(此处)折叠或打开

  1. ssh = paramiko.SSHClient()
  2. ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  3. ssh.connect('127.0.0.1',username='root',password='passw0rd')

  4. output=""
  5. err_str=""

  6. #cli_channel=ssh.get_transport().open_channel('session')
  7. #cli_channel.settimeout(3600)
  8. #cli_channel.get_pty()
  9. #cli_channel.invoke_shell()

  10. stdin, stdout, stderr = ssh.exec_command("/usr/bin/cp /root/names.txt /tmp/names.txt.before",get_pty=True)
  11. #stdin, stdout, stderr=ssh.exec_command("/usr/bin/cp /root/names.txt /tmp/names.txt.before; echo good bye; sleep 1; ls -al /dev; grep -Ev '^[[:space:]]*#' /usr/local/src/py/network/paramiko_2.py",get_pty=True)

  12. output=stdout.channel.recv(8192).decode()
  13. try:
  14.     while not stdout.channel.exit_status_ready():
  15.         rl, wl, xl = select.select([stdout.channel], [], [], 0.0)
  16.         if len(rl) > 0:
  17.             output+=stdout.channel.recv(8192).decode()
  18. except socket.timeout:
  19.     print("Operation time out. Ouput might not be complete.")

  20. rc=stdout.channel.recv_exit_status()
  21. print(rc)
  22. print(output)
  23. print(err_str)

  24. print ("Command done, closing SSH connection")
  25. ssh.close()


点击(此处)折叠或打开

  1. 0
  2. bash: /usr/bin/cp: No such file or directory
  3. good bye
  4. total 4
  5. drwxr-xr-x 20 root root 3200 Nov 14 09:19 .
  6. drwxr-xr-x 22 root root 4096 Oct 18 22:26 ..
  7. crw------- 1 root root 10, 235 Nov 14 09:19 autofs
  8. drwxr-xr-x 2 root root 140 Nov 14 17:19 block

综上我基本上有了这么几点体会:
select很难用
在对loopback 网络执行完exec_command()要去recv,否则stdout 或者stderr 可能会被遗漏。 在对外部IP执行SSH操作的时候应该不是问题。
其实第一个版本也没有看起来那么乱,而我用select 来尝试实现的时候,有时候直接用client对象的exec_command() 方法,有时候用channel的exec_command()方法,而在client 对象的exec_command()里面加入get_ptry=True 这个参数,会让脚本的输出更像shell的输出。channel 类有类似的方法,get_pty() 和invoke_shell()方法,但是如果使用了这两个方法后,我觉得就不应该再调用channel的exec_command()方法了。
使用paramiko 最好命令不要组合,分号意味着shell输入了多个命令,只是前面命令的return code 被遗弃了。 只有最后一个命令的return code 被返回。
这里会不会让你想起 bash的pipefail 特性。
使用管道连接命令是将整个命令作为一个整体返回最后一个命令的返回值,set -o pipefail 则会使管道命令失败会返回失败命令的值。

阅读(2747) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~