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,如需转载请自行联系原作者

相关文章
|
7天前
|
iOS开发 MacOS Windows
|
4天前
|
NoSQL Linux Redis
Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍
Redis的介绍,以及Redis的安装(本机windows版,虚拟机Linux版)和Redis常用命令的介绍
17 0
|
6天前
|
Ubuntu Python
在Ubuntu下载Python3.6 并建立软连接
在Ubuntu下载Python3.6 并建立软连接
9 0
|
6天前
|
安全 Linux Python
Volatility3内存取证工具安装及入门在Linux下的安装教程
Volatility3内存取证工具安装及入门在Linux下的安装教程
Volatility3内存取证工具安装及入门在Linux下的安装教程
|
6天前
|
数据可视化 小程序 Linux
【Linux】自动化构建工具make/Makefile和git介绍
【Linux】自动化构建工具make/Makefile和git介绍
13 0
|
7天前
|
缓存 数据库 索引
everything 本地文件搜索工具 完胜WIndows搜索 速度99% 超级给力
everything 本地文件搜索工具 完胜WIndows搜索 速度99% 超级给力
|
8天前
|
缓存 Linux
linux性能分析之内存分析(free,vmstat,top,ps,pmap等工具使用介绍)
这些工具可以帮助你监视系统的内存使用情况、识别内存泄漏、找到高内存消耗的进程等。根据具体的问题和需求,你可以选择使用其中一个或多个工具来进行内存性能分析。注意,内存分析通常需要综合考虑多个指标和工具的输出,以便更好地理解系统的行为并采取相应的优化措施。
28 6
|
11天前
|
监控 安全 虚拟化
深入浅出:NSSM封装Windows服务工具的使用与介绍
深入浅出:NSSM封装Windows服务工具的使用与介绍
21 3
|
11天前
|
域名解析 JSON API
Python小工具包
【5月更文挑战第2天】构建了一个Python小工具包,包含获取IP、域名解析、JSON格式化和时间戳转换的函数。通过`get_ip_address`和`resolve_domain`实现网络任务,`format_json`用于美化JSON数据,`timestamp_to_datetime`转换时间戳。这些函数可提高开发效率,易于整合到项目中,展现Python的简洁和灵活性。
21 1
Python小工具包