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

相关文章
|
5天前
|
Linux 应用服务中间件 nginx
|
5天前
|
Ubuntu Linux Shell
Linux系统命令 安装和文件相关命令
本文档详细介绍了Linux系统中的常用命令,包括软件安装卸载命令如`dpkg`和`apt-get`,压缩与解压命令如`gzip`、`bzip2`和`xz`,以及`tar`命令用于打包和解包。此外还介绍了文件分割命令`split`,文件操作命令如`cat`、`head`、`tail`、`more`、`less`等,管道命令和`wc`、`grep`、`find`、`cut`、`sort`、`uniq`、`diff`等实用工具。最后,文档还讲解了文件属性相关的命令如`chmod`、`chown`、`chgrp`以及创建硬链接和软链接的`ln`命令。
|
5天前
|
Linux Shell 网络安全
Linux 用户管理命令
本文详细介绍了Linux系统中的各类常用命令,包括用户管理(如`adduser`, `usermod`, `passwd`等)、系统操作(如关机、重启、注销)、磁盘管理(如`df`, `mkfs`, `mount`)及网络管理(如`ifconfig`, `ping`, `ssh`)等。通过具体示例展示了每个命令的基本用法和应用场景,帮助用户更好地理解和掌握Linux系统的管理和操作技巧。
|
5天前
|
存储 Ubuntu Linux
linux中的find 命令详细用法
本文介绍了如何将 `find` 命令与 `exec` 结合使用,通过具体示例展示了多种应用场景,如显示文件属性、重命名文件、收集文件大小、删除特定文件、执行工具、更改文件所有权和权限、收集 MD5 值等。文章还探讨了 `{} \;` 和 `{} +` 的区别,并演示了如何结合 `grep` 命令进行内容搜索。最后,介绍了如何在一个 `find` 命令中使用多个 `exec` 命令。这为 Linux 用户提供了强大的文件管理和自动化工具。
|
5天前
|
Shell Linux API
C语言在linux环境下执行终端命令
本文介绍了在Linux环境下使用C语言执行终端命令的方法。首先,文章描述了`system()`函数,其可以直接执行shell命令并返回结果。接着介绍了更强大的`popen()`函数,它允许程序与命令行命令交互,并详细说明了如何使用此函数及其配套的`pclose()`函数。此外,还讲解了`fork()`和`exec`系列函数,前者创建新进程,后者替换当前进程执行文件。最后,对比了`system()`与`exec`系列函数的区别,并针对不同场景推荐了合适的函数选择。
下一篇
DDNS