2022年06月09日更新:python实现批量操作主机(自己可以选择交互式和非交互式两种)


代码开始------------------------------------------------------------

1 #!/usr/bin/env python
  2 # _*_ coding:utf-8 _*_
  3 from paramiko import SSHClient, AutoAddPolicy
  4 import select
  5 import re
  6 
  7 host_list = [
  8     {'hostname': '192.168.174.130', 'port': 22, 'username': 'root', 'password': '2021'}
  9 ]
 10 conn_deon = []  # 第一次验证主机成功的主机列表
 11 conn_fail = []  # 第一次验证主机报错的主机列表
 12 conn_amend = []  # 修改端口后的主机列表;说明:修改端口的列表修改的是“第一次验证失败的列表【conn_fail】”
 13 host_version_filtration = []  # 过滤符合版本的主机;扩展功能
 14 no_host_version_filtration = []  # 过滤不符合版本的主机;扩展功能
 15 close_ssh_conn = []  # 在交互式操作主机,如果执行退出命令,它就会把需要退出的主机连接,加入到这个列表;
 16 
 17 
 18 def ssh_ex(hostname, port, username, password):
 19     '''
 20     这是一个测试连接是否成功的函数;
 21     会将成功连接的主机传入“conn_deon“列表;
 22     将连接失败的主机登录信息追加到“conn_fail”列表
 23     :param hostname: 测试主机IP
 24     :param port: 测试主机端口
 25     :param username: 测试主机用户名
 26     :param password: 测试主机密码
 27     :return:
 28     '''
 29     ssh_client = SSHClient()  # 创建一个连接实例
 30     ssh_client.set_missing_host_key_policy(AutoAddPolicy)  # 自动处理SSH弹出的yes/no
 31     try:
 32         ssh_client.connect(hostname, port, username, password)  # 进行SSH连接
 33         # 将成功连接的主机追加“conn_deon“列表
 34         conn_deon.append({'hostname': hostname, 'port': port,
 35                           'username': username, 'password': password})
 36         print('\033[0;32mhost:\033[0m%s' % hostname)
 37         ssh_client.close()  # 关闭连接
 38     except Exception as e:
 39         print('\033[0;31mhost:\033[0m%s' % hostname)  # 打印连接失败的主机
 40         # 将连接失败的主机登录信息追加到“conn_fail”列表
 41         conn_fail.append({'hostname': hostname, 'port': port,
 42                           'username': username, 'password': password})
 43         print(e)  # 打印错误
 44         print('', end='\n\n')
 45 
 46 
 47 def ssh_host_filtration(hostname, port, username, password,filtration_parameter):
 48     '''
 49     这是一个过滤函数;是一个扩展功能;
 50     :param hostname:主机IP
 51     :param port:登录端口
 52     :param username:用户名
 53     :param password:用户密码
 54     :param filtration_parameter:过滤参数
 55     :return:如果输入的命令正确,return就会进行返回;反之return就会返回空数据;
 56     '''
 57     ssh_client = SSHClient()
 58     ssh_client.set_missing_host_key_policy(AutoAddPolicy)
 59     ssh_client.connect(hostname, port, username, password)
 60     print('\033[0;32mhost:\033[0m%s开始过滤...' % hostname)
 61     stdin, stdout, stderr = ssh_client.exec_command(filtration_parameter)
 62     result_out = stdout.read().decode('utf-8')
 63     result_err = stderr.read().decode('utf-8')
 64     ssh_client.close()
 65     return result_out
 66 
 67 
 68 def ssh_ex_official(hostname, port, username, password):
 69     '''
 70     这是一个进行批量操作主机的函数;
 71     :param hostname: 测试主机IP
 72     :param port: 测试主机端口
 73     :param username: 测试主机用户名
 74     :param password: 测试主机密码
 75     :return:
 76     '''
 77     ssh_client = SSHClient()
 78     ssh_client.set_missing_host_key_policy(AutoAddPolicy)
 79     ssh_client.connect(hostname, port, username, password)
 80     print('\033[0;32mhost:\033[0m%s' % hostname)
 81     while True:
 82         cmd = yield
 83         if cmd != 'quit_shell':
 84             stdin, stdout, stderr = ssh_client.exec_command(cmd)
 85             result = stdout.read().decode('utf-8')
 86             err = stderr.read().decode('utf-8')
 87             if len(err) != 0:
 88                 print(err)  # 打印错误
 89                 print('\033[0;32m%s\033[0m' % hostname, '^' * 100)
 90             else:
 91                 print(result)  # 打印服务端主机返回信息
 92                 print('\033[0;32m%s\033[0m' % hostname, '^' * 100)
 93         else:
 94             print('主机“%s”已退出连接...' % hostname)
 95             ssh_client.close()
 96 
 97 
 98 def ssh_interaction_official(hostname, port, username, password):
 99     '''
100     这是一个交互式的SSH函数;
101     :param hostname:
102     :param port:
103     :param username:
104     :param password:
105     :return:
106     '''
107     ssh_shell = SSHClient()
108     ssh_shell.set_missing_host_key_policy(AutoAddPolicy)
109     ssh_shell.connect(hostname, port, username, password)
110     chan = ssh_shell.invoke_shell()
111     inputs = [chan, ]
112     outputs = []
113     while True:
114         while True:
115             readable, writeable, exceptional = select.select(inputs, outputs, inputs, 1)  # 解决打印完整的问题;
116             # 但是select会进行阻塞;关于select.select()函数超时,只要超时就会向下执行;
117 
118             if readable:
119                 info = chan.recv(1024).decode('utf-8')
120                 print(info)
121                 continue
122             else:
123                 print('\033[0;32m%s\033[0m' % hostname, '^' * 100)
124                 break
125 
126         cmd = yield
127         if cmd != 'quit_shell':
128             chan.send(cmd.encode('utf-8') + b'\n')
129         else:
130             print('“%s”主机退出连接...' % hostname)
131             # 这段代码“print('“%s”主机退出连接...' % hostname)”非常关键,如果下面继续增加“break”就会出现报错;
132             # 添加break报错内容是“File "", line 1, in  StopIteration";
133             # 添加“ssh_shell.close()”代码会进入一个死循环,一直打印空白行;
134             # 所以仅仅只有“print('“%s”主机退出连接...' % hostname)”这段代码就够了;
135             close_ssh_conn.append(ssh_shell)  # 这是后期“2022-5-11”添加的代码,因为上面的直接关闭连接
136             # 出现报错,所以在退出的时候把连接加入一个列表,当所有的连接都执行完
137             # print('“%s”主机退出连接...' % hostname)后
138             # 在进行for循环遍历这些需要close的连接进行关闭,这样就解决了无法关闭连接或者关闭连接报错的问题!
139 
140 
141 def bath_handle_host_ex(input_list):
142     '''
143     这是一个“非交互式”批量操作主机发送操作命令的函数;
144     :param input_list: 传入验证用户通过的字典信息
145     :return:
146     '''
147     ssh_conn_list = []  # 将生成的多个批量变量加入这个列表
148     count_1 = 1
149     for i in input_list:
150         print('开始连接第%s个主机...' % count_1)
151         hostname = i['hostname']
152         port = i['port']
153         username = i['username']
154         password = i['password']
155         # 将批量生成的遍历加入“ssh_conn_list”列表
156         ssh_conn_list.append("conn%s = ssh_ex_official('%s', %s, '%s', '%s')"
157                              % (input_list.index(i), hostname, port, username, password))
158         count_1 += 1
159     print('所有主机准备就绪...回车继续')
160     input()
161     for i in ssh_conn_list:
162         exec(i)  # 执行遍历出的【conn%s = ssh_ex_official('%s', %s, '%s', '%s')】变量
163     for_number = len(input_list)  # 将遍历的次数进行赋值
164     for i in range(for_number):
165         exec('next(conn%s)' % i)  # exec函数是一个将字符串类型的代码进行执行
166     while True:
167         print('', end='\n\n\n')
168         cmds = input('请输入需要批量处理的命令>>:').strip()  # strip去掉开头和结尾的回车和空格
169         if len(cmds) == 0:
170             continue
171         else:
172             for i in range(for_number):
173                 exec('conn%s.send(cmds)' % i)
174             if cmds == 'quit_shell':
175                 input('返回上级菜单')
176                 break
177 
178 
179 def bath_handle_host_interaction(input_list):
180     '''
181     这是一个“交互式”批量操作主机发送操作命令的函数;
182     :param input_list: 传入验证用户通过的字典信息
183     :return:
184     '''
185     ssh_conn_list = []  # 将生成的多个批量变量加入这个列表
186     count_1 = 1
187     for i in input_list:
188         print('开始连接第%s个主机...' % count_1)
189         hostname = i['hostname']
190         port = i['port']
191         username = i['username']
192         password = i['password']
193         # 将批量生成的遍历加入“ssh_conn_list”列表
194         ssh_conn_list.append("conn%s = ssh_interaction_official('%s', %s, '%s', '%s')"
195                              % (input_list.index(i), hostname, port, username, password))
196         count_1 += 1
197     print('所有主机准备就绪...回车继续')
198     input()
199     for i in ssh_conn_list:
200         exec(i)  # 执行遍历出的【conn%s = ssh_ex_official('%s', %s, '%s', '%s')】变量
201     for_number = len(ssh_conn_list)  # 将遍历的次数进行赋值
202     for i in range(for_number):
203         exec('next(conn%s)' % i)  # exec函数是一个将字符串类型的代码进行执行
204     while True:
205         print('', end='\n\n\n')
206         cmds = input('请输入需要批量处理的命令>>:').strip()  # strip去掉开头和结尾的回车和空格
207         if len(cmds) == 0:
208             continue
209         else:
210             for i in range(for_number):
211                 exec('conn%s.send(cmds)' % i)
212             if cmds == 'quit_shell':
213                 input('返回上级菜单')
214                 break
215 
216 
217 def test_login(print_info, host_list_info):
218     print(print_info, '-' * 30)
219     count = 1
220     for i in host_list_info:
221         print('开始进行第%s个主机用户验证...' % count)
222         ssh_ex(hostname=i['hostname'], port=i['port'],
223                username=i['username'], password=i['password'])
224         count += 1
225     print('连接成功主机列表', '-' * 20)
226     conn_count = 0
227     for i in conn_deon:
228         print(i['hostname'])
229         conn_count += 1
230     return conn_count
231 
232 
233 if __name__ == '__main__':
234     while True:
235         print('''
236 ---------------------------------------------------------------------------
237 功能说明:
238     1.登录过滤主机
239     2.批量操作主机
240     3.修改过滤后主机端口
241     4.查看脚本使用说明
242     提示:选择输入对应的功能编号!!!
243 ---------------------------------------------------------------------------
244         ''')
245         func_in = input('输入对应的功能编号:')
246         if func_in == '1':
247             while True:
248                 test_port = input('''
249 1.登录过滤主机-----------------------------------
250 
251 请输入需要验证登录的主机列表
252 提示只可以输入:
253 1.验证原始主机是否可以登录
254 2.验证修改端口后的主机是否可以登录
255 "quit"返回上一级
256 >>:
257                 ''')
258                 if test_port == '1':
259                     del conn_deon[:]
260                     number = test_login(print_info='验证所有原始主机信息列表', host_list_info=host_list)
261                     input('''
262 验证完成...
263 \033[0;32m成功连接%s个主机\033[0m
264 回车继续
265                         ''' % number)
266                     break
267                 elif test_port == '2':
268                     count = 0
269                     for i in conn_deon:
270                         count += 1
271                     number = test_login(print_info='验证修改端口后的主机列表', host_list_info=conn_amend)
272                     new_number = count - number
273                     input('''
274 验证完成...
275 \033[0;32m修改SSH连接端口后成功连接%s个主机\033[0m
276 回车继续
277                         ''' % new_number)
278                     break
279                 elif test_port == 'quit':
280                     break
281                 else:
282                     continue
283         elif func_in == '2':
284             while True:
285                 handle_list = input('''
286 2.批量操作主机-----------------------------------
287 
288 请输入需要批量操作的主机列表
289 提示只可以输入:
290 1.开始非交互批量操作主机
291 2.开始交互式批量操作主机
292 3.过滤符合条件的主机加入新列表进行批量操作;注意这是一个扩展功能
293 "quit"返回上一级
294 >>:
295                 ''')
296                 if handle_list == '1':
297                     input('''
298 \033[0;31m提示:输入“quit_shell”就会退出批量操作主机\033[0m
299 回车继续
300                     ''')
301                     bath_handle_host_ex(conn_deon)
302                 elif handle_list == '2':
303                     input('''
304 \033[0;31m提示:输入“quit_shell”就会退出批量操作主机\033[0m
305 回车继续
306                     ''')
307                     bath_handle_host_interaction(conn_deon)309                     for i in close_ssh_conn:
310                         i.close()
311                 elif handle_list == '3':
312                     for i in conn_deon:
313                         hostname = i['hostname']
314                         port = i['port']
315                         username = i['username']
316                         password = i['password']
317                         re_parameter = ssh_host_filtration(hostname=hostname, port=port, username=username,
318                                                  password=password,
319                                                  filtration_parameter='cat /etc/.kyinfo |grep milestone')
320                         if re_parameter:
321                             re_version = '\d\.\d'
322                             res = re.search(re_version, re_parameter, re.I).group()
323                             if res == '3.3':
324                                 host_version_filtration.append(i)
325                             else:
326                                 no_host_version_filtration.append({'hostname':hostname, '系统版本':res})
327                         else:
328                             no_host_version_filtration.append({'hostname':hostname, '系统版本':'非麒麟系统'})
329                     if len(host_version_filtration) == 0:
330                         print('', end='\n\n\n')
331                         print('没有符合过滤条件的主机...')
332                         print('\033[0;31m不符合主机过滤规则的主机和版本如下', '-' * 30)
333                         for i in no_host_version_filtration:
334                             for k in i:
335                                 print('\033[0;31m%s\033[0m' % i[k], end=' ')
336                             print('')
337                         input('回车继续')
338                         del no_host_version_filtration[:]
339                     else:
340                         bath_handle_host_interaction(host_version_filtration)
341                         print('', end='\n\n\n')
342                         print('\033[0;31m不符合主机过滤规则的主机和版本\033[0m', '-' * 30)
343                         for i in no_host_version_filtration:
344                             for k in i:
345                                 print('\033[0;31m%s\033[0m' % i[k], end=' ')
346                             print('')
347                         input('回车继续')
348                         del no_host_version_filtration[:]
349                         del host_version_filtration[:]
350                 elif handle_list == 'quit':
351                     break
352                 else:
353                     print('输入的列表名存在错误!请重新输入!')
354                     continue
355         elif func_in == '3':
356             print('3.修改过滤主机端口-----------------------------------')
357             func_in_3 = input('''
358 是否开始修改连接失败的端口号?
359 yes/no
360 >>:
361             ''')
362             if func_in_3 == 'yes':
363                 port_new = int(input('请输入新的端口号:'))
364                 for i in conn_fail:
365                     i['port'] = port_new
366                     conn_amend.append(i)
367                 print('\033[0;32m-\033[0m' * 90)
368                 count = 0
369                 for i in conn_amend:
370                     print(i)
371                     count += 1
372                 input('''
373 已完成主机列表的端口\033[0;32m%s\033[0m修改;
374 共计:完成\033[0;32m%s\033[0m个
375 回车继续
376                 ''' % (port_new, count))
377             else:
378                 continue
379         elif func_in == '4':
380             print('4.查看脚本使用说明-----------------------------------')
381             input('''
382 本脚本使用步骤:
383         1.运行‘1.1’过滤主机
384         2.运行‘3’修改过滤后的主机端口
385         3.运行‘1.2’继续过滤主机
386         4.运行‘2.1’开始批量操作主机
387         回车返回上级菜单...
388             ''')
389         else:
390             exit(0)

代码结束------------------------------------------------------------

使用方法:

1.每使用一次脚本就需要执行1.1过滤一次主机

2.一般批量操作主机使用2.2交互式操作就可以了

3.代码缺点:它不是多线程的,for循环比较慢-需要一个一个为主机下发命令;

      另外一个缺点就是,主机账号信息需要手动输入host_list列表中;

      host_list中的信息不是使用hash加密的,存在安全隐患;

      你可以自己写一个登录加密的列表进行给它加密;