Python实现linux/windows通用批量‘命令/上传/下载’小工具

简介:

这阵子一直在学python,碰巧最近想把线上服务器环境做一些规范化/统一化,于是便萌生了用python写一个小工具的冲动。就功能方面来说,基本上是在“重复造轮子”吧,但是当我用这小工具完成了30多台服务器从系统层面到应用层面的一些规范化工作之后,觉得效果还不算那么low(高手可忽略这句话~~),这才敢拿出来跟小伙伴们分享一下。

(注:笔者所用为python版本为3.5,其他版本未经测试~~)

其实很简单,就"一个脚本"+"server信息文件"实现如题目所述的功能,能够像使用linux系统命令一样拿来即用。来看一些基本的使用截图吧

帮助信息:
wKioL1ecuRjhZOcbAAB726fGrJ8967.png

批量执行远程命令:

wKiom1ecuU3DWCOkAAAcECLpQRM543.png

上传单个文件:wKioL1ecsILimkzjAAAcMSRfuhU032.png

上传目录:

wKioL1ecuWWz6RXOAABgZwSs0fs274.png

下载单个文件:

wKioL1ectNCy078nAAAZGxGlF6s564.png

下载目录:

wKiom1ecwVOyS14CAACGrT3hr7w463.png

接下来直接看代码吧(我的老(lan)习惯,代码里注释还算详细,所以我就懒得再解释那么多喽)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#!/bin/env python3
# coding:utf-8
"""
by ljk 20160704
"""
from  paramiko  import  SSHClient, AutoAddPolicy
from  os  import  path, walk, makedirs
from  re  import  split, match, search
from  sys  import  exit
from  argparse  import  ArgumentParser, RawTextHelpFormatter
 
# ----------
# get_args()函数通过argparse模块的ArgumentParser类来生成帮助信息并获取命令行参数
# 生成一个全局变量字典对象args,保存处理过的命令行参数
# ----------
def  get_args():
     """实例化类,formatter_class参数允许help信息以自定义的格式显示"""
     parser  =  ArgumentParser(description = "This is a tool for execute command(s) on remote server(s) or get/put file(s) from/to the remote server(s)\nNotice: please always use '/' as path separater!!!" ,formatter_class  = RawTextHelpFormatter,epilog = "Notice:\n  If any options use more than once,the last one will overwrite the previous" )
     parser.add_argument( '-u' ,metavar = 'USER' ,dest = 'user' , help = "remote username" ,required = True )
     parser.add_argument( '-p' ,metavar = 'PASSWORD' ,dest = 'passwd' , help = " user's password" )
     parser.add_argument( '--pkey' ,nargs = '?' ,metavar = 'PRIVATE KEY' ,dest = 'pkey' , help = "local private key,if value not followed by this option,the default is: ~/.ssh/id_rsa" ,default = None ,const = '%s/.ssh/id_rsa'  %  path.expanduser( '~' ))
     parser.add_argument( '--server' , metavar = 'SERVER_INFO_FILE' help = "file include the remote server's information\nwith the format of 'name-ip:port',such as 'web1-192.168.1.100:22',one sever one line" , required = True )
     remote_command  =  parser.add_argument_group( 'remote command' , 'options for running remote command' )
     remote_command.add_argument( '--cmd' ,metavar = '“COMMAND”' ,dest = 'cmd' , help = "command run on remote server,multiple commands sperate by ';'" )
     sftp  =  parser.add_argument_group( 'sftp' , 'options for running sftp' )
     sftp.add_argument( '--put' ,metavar = '', help = "transfer from local to remote" ,nargs = 2 )
     sftp.add_argument( '--get' ,metavar = '', help = "transfer from remote to local" ,nargs = 2 )
     # 全局字典 键(add_argument()中的dest):值(用户输入)
     # vars将Namespace object转换成dict object
     global  args
     args  =  vars (parser.parse_args())
     # 判断 --cmd  --put  --get 三个参数的唯一性
     # 清除掉args字典中值为None的项.argparse默认给不出现的值赋值None
     =  0
     for  in  ( 'cmd' , 'put' , 'get' ):
         if  in  args:
             if  args[i]  is  None :
                 del  args[i]
             else :
                 n + = 1
     if  n >  1 :
         print ( '\n  Only one of the "--cmd --put --get" can be used!' )
         exit( 10 )
 
def  get_ip_port(fname):
     """从制定文件(特定格式)中,取得主机名/主机ip/端口"""
     try :
         fobj  =  open (fname, 'r' )
     except  Exception as err:
         print (err)
         exit( 10 )
     for  line  in  fobj.readlines():
         if  line ! =  '\n'  and  not    match( '#' ,line):    # 过滤空行和注释行
             list_tmp  =    split( '[-:]' ,line)
             server_name  =  list_tmp[ 0 ]
             server_ip  =  list_tmp[ 1 ]
             port  =  int (list_tmp[ 2 ])
             yield  (server_name,server_ip,port)
 
def  create_sshclient(server_ip,port):
     """根据命令行提供的参数,建立到远程server的ssh链接.这里本在run_command()函数内部。
     摘出来的目的是为了让sftp功能也通过sshclient对象来创建sftp对象,因为初步观察t.connect()方法在使用key时有问题"""
     global  client
     client  =  SSHClient()
     client.set_missing_host_key_policy(AutoAddPolicy())
     try :
         client.connect(server_ip,port = port,username = args[ 'user' ],password = args[ 'passwd' ],key_filename = args[ 'pkey' ])
     except  Exception as err:     # 有异常,打印异常,并返回'error'
         print ( '{}----{} error: {}' . format ( ' ' * 4 ,server_ip,err))
         return  'error'
 
# ----------
# run_command()执行远程命令
# ----------
def  run_command():
     """执行远程命令的主函数"""
     # stdout 假如通过分号提供单行的多条命令,所有命令的输出(在linux终端会输出的内容)都会存储于stdout
     # 据观察,下面三个变量的特点是无论"如何引用过一次"之后,其内容就会清空
     # 有readlines()的地方都是流,用过之后就没有了
     stdin,stdout,stderr  =  client.exec_command(args[ 'cmd' ])
     copy_out,copy_err  =  stdout.readlines(),stderr.readlines()
     if  len (copy_out) ! =  0 :
         print ( '%s----result:'  %  ( ' ' * 8 ))
         for  in  copy_out:
             print ( '%s%s'  %  ( ' ' * 12 ,i),end = '')
     elif  len (copy_err) ! =  0 :
         print ( '%s----error:'  %  ( ' ' * 8 ))
         for  in  copy_err:
             print ( '%s%s'  %  ( ' ' * 12 ,i),end = '')
         exit( 10 )
     client.close()
 
# ----------
# sftp_transfer() 远程传输文件的主函数
# ----------
def  sftp_transfer(source_path,destination_path,method):
     """文件传输的 主函数"""
     sftp  =  client.open_sftp()
 
     # 下面定义sftp_transfer()函数所需的一些子函数
     def  str_to_raw(s):
         """
         !!此函数暂未使用,参数中的目录强制使用'/'作为分隔符!!
         借用网友的代码,将会被反斜杠转义的字符做转换.将\转换为\\,这里的转换还不全,比如对'\123'这样的还无法转换成'\\123'
         """
         raw_map  =  { 8 :r '\b' 7 :r '\a' 12 :r '\f' 10 :r '\n' 13 :r '\r' 9 :r '\t' 11 :r '\v' }
         return  r''.join(i  if  ord (i) >  32  else  raw_map.get( ord (i), i)  for  in  s)
 
     def  process_arg_dir(src,dst):
         """处理目录时,自动检查用户输入,并在s_path和d_path后面都加上/"""
         if  not  src.endswith( '/' ):
             src  =  src  +  '/'
         if  not  dst.endswith( '/' ):
             dst  =  dst  +  '/'
         return  src,dst
 
     def  sftp_put(src, dst, space):
         """封装sftp.put"""
         try :
             sftp.put(src, dst)
             print ( '%s%s'  %  ( ' '  *  space, src))
         except  Exception as err:
             print ( '%s----Uploading %s Failed'  %  ( ' '  *  space, src))
             print ( '{}----{}' . format ( ' '  *  space, err))
             exit( 10 )
 
     def  sftp_get(src, dst, space):
         """封装sftp.get"""
         try :
             sftp.get(src, dst)
             print ( '%s%s'  %  ( ' '  *  space, src))
         except  Exception as err:
             print ( '%s----Downloading %s Failed'  %  ( ' '  *  space, src))
             print ( '{}----{}' . format ( ' '  *  space, err))
             exit( 10 )
 
     def  sftp_transfer_rcmd(cmd = None ,space = None ):
         """在sftp_transfer()函数内部执行一些远程命令来辅助其完成功能"""
         stdin,stdout,stderr  =  client.exec_command(cmd)
         copy_out, copy_err  =  stdout.readlines(), stderr.readlines()
         if  len (copy_err) ! =  0 :
             for  in  copy_err:
                 print ( '%s----%s'  %  ( ' ' * space,i),end = '')
             exit( 10 )
         else :
             return  copy_out
 
     def  check_remote_path(r_path):
         """通过client对象在远程linux执行命令,来判断远程路径是否存在,是文件还是目录"""
         check_cmd  =  'if [ -e {0} ];then if [ -d {0} ];then echo directory;elif [ -f {0} ];then echo file;fi;else echo no_exist;fi' . format (r_path)
         # check_cmd命令会有三种‘正常输出’directory  file  no_exist
         check_result  =  sftp_transfer_rcmd(cmd = check_cmd)[ 0 ].strip( '\n' )
         if  check_result  = =  'directory' :
             return  'directory'
         elif  check_result  = =  'file' :
             return  'file'
         else :
             return  'no_exist'
     # 子函数定义完毕
 
     # 上传逻辑
     if  method  = =  'put' :
         print ( '%s----Uploading %s TO %s'  %  ( ' ' * 4 ,source_path,destination_path))
         if   path.isfile(source_path):     # 判断是文件
             if  destination_path.endswith( '/' ):
                 """
                 put和get方法默认只针对文件,且都必须跟上文件名,否则会报错.
                 这里多加一层判断实现了目标路径可以不加文件名
                 """
                 sftp_transfer_rcmd(cmd = 'mkdir -p {}' . format (destination_path),space = 4 )     # 若目标路径不存在,则创建
                 destination_path  =  destination_path  +   path.basename(source_path)
                 sftp_put(source_path, destination_path,  8 )
             else :
                 sftp_transfer_rcmd(cmd = 'mkdir -p {}' . format ( path.dirname(destination_path)), space = 4 )
         elif   path.isdir(source_path):    # 判断是目录
             source_path,destination_path  =  process_arg_dir(source_path,destination_path)
             for  root, dirs, files  in   walk(source_path):
                 """通过 os.walk()函数取得目录下的所有文件,此函数默认包含 . ..的文件/目录,需要去掉"""
                 for  file_name  in  files:
                     s_file  =   path.join(root,file_name).replace( '\\',' / ')     # 逐级取得每个sftp client端文件的全路径,并将路径中的\换成/
                     if  not    search( '.*/\..*' , s_file):
                         """过滤掉路径中包含以.开头的目录或文件"""
                         d_file  =  s_file.replace(source_path,destination_path, 1 )     # 由local_file取得每个远程文件的全路径
                         d_path  =   path.dirname(d_file)
                         check_remote_path_result  =  check_remote_path(d_path)
                         if  check_remote_path_result  = =  'directory' :
                             sftp_put(s_file, d_file,  12 )   # 目标目录存在,直接上传
                         elif  check_remote_path_result  = =  'no_exist' :
                             print ( '%s----Create Remote Dir: %s'  %  ( ' '  *  8 ,  path.dirname(d_file)))
                             sftp_transfer_rcmd(cmd = 'mkdir -p {}' . format (d_path))
                             sftp_put(s_file, d_file,  12 )
                         else :
                             print ( '{}----the {} is file' . format ( ' '  *  8 , d_path))
                             exit( 10 )
         else :
             print ( '%s%s is not exist'  %  ( ' ' * 8 ,source_path))
             exit( 10 )
 
     # 下载逻辑
     elif  method  = =  'get' :
         print ( '%s----Downloading %s TO %s'  %  ( ' ' * 4 , source_path, destination_path))
         check_remote_path_result  =  check_remote_path(source_path)
         if  check_remote_path_result  = =  'file' :     # 判断是文件
             if  path.isfile(destination_path):
                 sftp_get(source_path, destination_path,  8 )
             else :     # 参数中的'目标路径'为目录或不存在
                 try :
                     makedirs(destination_path)
                     sftp_get(source_path,  path.join(destination_path, path.basename(source_path)).replace( '\\',' / '),  8 )
                 except  Exception as err:
                     print ( '%s----Create %s error'  %  ( ' ' * 4 ,destination_path))
                     print ( '{}{}' . format ( ' ' * 8 ,err))
                     exit( 10 )
             sftp_get(source_path,destination_path, 8 )
         elif  check_remote_path_result  = =  'directory' :     # 判断是目录
             source_path, destination_path  =  process_arg_dir(source_path, destination_path)
             def  process_sftp_dir(path_name):
                 """
                 此函数递归处理sftp server端的目录和文件,并在client端创建所有不存在的目录,然后针对每个文件在两端的全路径执行get操作.
                 path_name第一次的引用值应该是source_path的值
                 """
                 d_path  =  path_name.replace(source_path,destination_path, 1 )
                 if  not   path.exists(d_path):     # 若目标目录不存在则创建
                     print ( '%s----Create Local Dir: %s'  %  ( ' ' * 8 ,d_path))
                     try :
                          makedirs(d_path)     # 递归创建不存在的目录
                     except  Exception as err:
                         print ( '%s----Create %s Failed'  %  ( ' ' * 8 ,d_path))
                         print ( '{}----{}' . format ( ' ' * 8 ,err))
                         exit( 10 )
                 for  name  in  (i  for  in  sftp.listdir(path = path_name)  if  not  i.startswith( '.' )):
                     """去掉以.开头的文件或目录"""
                     s_file  =   path.join(path_name,name).replace( '\\',' / ')    # 在win环境下组合路径所用的' \\ '换成' / '
                     d_file  =  s_file.replace(source_path,destination_path, 1 )     # 目标端全路径
                     chk_r_path_result  =  check_remote_path(s_file)
                     if  chk_r_path_result  = =  'file' :     # 文件
                         sftp_get(s_file,d_file, 12 )
                     elif  chk_r_path_result  = =  'directory' :     # 目录
                         process_sftp_dir(s_file)     # 递归调用本身
             process_sftp_dir(source_path)
         else :
             print ( '%s%s is not exist'  %  ( ' '  *  8 , source_path))
             exit( 10 )
     client.close()
 
 
if  __name__  = =  "__main__" :
     try :
         get_args()
         for  server_name,server_ip,port  in  get_ip_port(args[ 'server' ]):     #循环处理每个主机
             print ( '\n--------%s'  %  server_name)
             if  create_sshclient(server_ip,port)  = =  'error' :
                 continue
             # 区别处理 --cmd --put --get参数
             if  'cmd'  in  args:
                 run_command()
             elif  'put'  in  args:
                 sftp_transfer(args[ 'put' ][ 0 ],args[ 'put' ][ 1 ], 'put' )
             elif  'get'  in  args:
                 sftp_transfer(args[ 'get' ][ 0 ],args[ 'get' ][ 1 ], 'get' )
     except  KeyboardInterrupt:
         print ( '\n-----bye-----' )

其实之所以想造这个“简陋”的轮子,一方面能锻炼python coding,另一方面当时确实有这么一个需求。而且用自己的工具完成工作也是小有成就的(请勿拍砖~)。

另外,在使用paramiko模块的过程中,又促使我深入的了解了一些ssh登陆的详细过程。所以作为一枚运维,现在开始深刻的理解到“运维”和“开发”这俩概念之间的相互促进。





     本文转自kai404 51CTO博客,原文链接:http://blog.51cto.com/kaifly/1832200,如需转载请自行联系原作者

相关文章
|
11月前
|
关系型数据库 虚拟化 UED
Omnissa Horizon Windows OS Optimization Tool 2503 - Windows 系统映像优化工具
Omnissa Horizon Windows OS Optimization Tool 2503 - Windows 系统映像优化工具
421 7
Omnissa Horizon Windows OS Optimization Tool 2503 - Windows 系统映像优化工具
|
6月前
|
安全 Linux iOS开发
SonarQube Server 2025 Release 5 (macOS, Linux, Windows) - 代码质量、安全与静态分析工具
SonarQube Server 2025 Release 5 (macOS, Linux, Windows) - 代码质量、安全与静态分析工具
293 0
SonarQube Server 2025 Release 5 (macOS, Linux, Windows) - 代码质量、安全与静态分析工具
|
12月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
3282 77
|
7月前
|
Ubuntu Linux
Ubuntu Linux 20.04 LTS “Focal Fossa”测试版开放下载
u要知道有关新系统的更多信息,大家可以前往Ubuntu Wiki页面,其中包含Ubuntu 20.04的发行说明。
191 0
|
7月前
|
Ubuntu 安全 Linux
Linux系统-Ubuntu的下载和安装 软件大全
在庄子看来,生老病死就像四时交替,都只是自然现象而已,人不必有什么忧虑,也不必有什么痛苦。人源于自然,再回归自然,说到底,不过是正常的生死轮转,他依然在天地之间,只要怀着这样的想法,人又有什么好痛苦的呢。
|
7月前
|
Ubuntu Linux Windows
Ubuntu Linux 24.04 LTS 发行版现已开放下载
Ubuntu 24.04 采用了 Linux 6.8 内核,可利用 Netplan 在桌面上配置网络连接,还配备了现代化的桌面操作系统安装程序,还带来了新版 Ubuntu 字体以及各种性能优化以及大量新功能。
|
10月前
|
消息中间件 NoSQL Linux
Redis的基本介绍和安装方式(包括Linux和Windows版本),以及常用命令的演示
Redis(Remote Dictionary Server)是一个高性能的开源键值存储数据库。它支持字符串、列表、散列、集合等多种数据类型,具有持久化、发布/订阅等高级功能。由于其出色的性能和广泛的使用场景,Redis在应用程序中常作为高速缓存、消息队列等用途。
994 16
|
12月前
|
网络协议 Linux 网络安全
微软工程师偷偷在用!这款SSH工具让Windows操控CentOS比Mac还优雅!
远程登录Linux服务器是管理和维护服务器的重要手段,尤其在远程办公、云服务管理等场景中不可或缺。通过工具如XShell,用户可以方便地进行远程管理。SSH协议确保了数据传输的安全性,命令行界面提高了操作效率。配置XShell连接CentOS时,需确保Linux系统开启sshd服务和22端口,并正确设置主机地址、用户名和密码。此外,调整字体和配色方案可优化使用体验,解决中文显示问题。
505 21
微软工程师偷偷在用!这款SSH工具让Windows操控CentOS比Mac还优雅!
|
12月前
|
自然语言处理 数据库 iOS开发
DBeaver Ultimate Edtion 25.0 Multilingual (macOS, Linux, Windows) - 通用数据库工具
DBeaver Ultimate Edtion 25.0 Multilingual (macOS, Linux, Windows) - 通用数据库工具
814 12
DBeaver Ultimate Edtion 25.0 Multilingual (macOS, Linux, Windows) - 通用数据库工具