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 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)StopIteration";
代码结束------------------------------------------------------------
使用方法:
1.每使用一次脚本就需要执行1.1过滤一次主机
2.一般批量操作主机使用2.2交互式操作就可以了
3.代码缺点:它不是多线程的,for循环比较慢-需要一个一个为主机下发命令;
另外一个缺点就是,主机账号信息需要手动输入host_list列表中;
host_list中的信息不是使用hash加密的,存在安全隐患;
你可以自己写一个登录加密的列表进行给它加密;