使用方法

  1. (py-ssh) py-ssh python tmp.py -h
  2. usage: tmp.py [-h] [--user USER] [--password PASSWORD] [--port PORT] [--addresses IPS [IPS ...]] [--cmd CMD] [--sem SEM] [--down_host DOWN_HOST] [--files FILES [FILES ...]] [--dst_path DST_PATH]
  3. optional arguments:
  4. -h, --help show this help message and exit
  5. --user USER, -u USER Specify ssh username
  6. --password PASSWORD, -p PASSWORD
  7. Specify ssh password
  8. --port PORT, -P PORT Specify ssh port;default 22
  9. --addresses IPS [IPS ...], -a IPS [IPS ...]
  10. Specify ssh hosts
  11. --cmd CMD, -c CMD Specify cmd for execute
  12. --sem SEM, -s SEM Number of threads; default 5
  13. --down_host DOWN_HOST
  14. Download file server address
  15. --files FILES [FILES ...], -f FILES [FILES ...]
  16. Specify files use for copy
  17. --dst_path DST_PATH, -d DST_PATH
  18. File storage directory
  19. --dir DIR Specify storage files directory

参数解释

  • user ssh登陆用户
  • password ssh登陆密码
  • port ssh登陆端口号
  • addresses 需要登陆的主机清单,支持单主机和多主机, —addresses 192.168.50.163 192.168.50.160:192.168.50.163
  • cmd 执行的命令
  • sem 并发的信号量
  • down_host 用于下载文件的地址, 示例: 192.168.1.10:8000
  • files 需要进行拷贝到远程主机的文件, 必须搭配 dst_path 使用
  • dst_path 拷贝到远程主机的目录
  • dir 用于存放需要被拷贝的文件,先拷贝 files 然后 dir 里面的文件,统一放到 dstz_path下面

用法示例

  1. python tmp.py --user cortex \
  2. --addresses 192.168.50.163 192.168.50.160:192.168.51.163 \
  3. --password cortex \
  4. --files /Users/wuxing/install.sh /Users/wuxing/minicom.log \
  5. --dst_path /tmp \
  6. --down_host 192.168.50.101:8000 \
  7. --cmd 'ls -l /tmp' \
  8. --dir files \
  9. --sem 10

脚本

  1. import getpass
  2. import hashlib
  3. import os
  4. from concurrent.futures import ThreadPoolExecutor
  5. from time import perf_counter
  6. import sys
  7. import threading
  8. import paramiko
  9. from time import sleep
  10. from loguru import logger as log
  11. import argparse
  12. from ipaddress import ip_address
  13. log.remove()
  14. log.add(sys.stderr, level="INFO")
  15. log.add("run.log", level="INFO", mode="w")
  16. log.add("error.log", level="ERROR", mode="w")
  17. class SSHConnection(threading.Thread):
  18. def __init__(self, **kwargs):
  19. super(SSHConnection, self).__init__()
  20. self.username = kwargs.get('user')
  21. self.host = kwargs.get('host')
  22. self.port = kwargs.get('port')
  23. self.password = kwargs.get('password')
  24. self.keyfile = self.get_keyfile()
  25. self.sem = kwargs.get('sem')
  26. self.files = kwargs.get('files')
  27. self.down_host = kwargs.get('down_host')
  28. self.dst_path = kwargs.get('dst_path')
  29. self.cmd = kwargs.get('cmd')
  30. self.dir = kwargs.get('dir')
  31. log.info(f"directory: {self.dir}")
  32. if self.dir:
  33. if not os.path.exists(self.dir):
  34. log.error(f"{self.dir} is not exists")
  35. sys.exit(0)
  36. def get_keyfile(self, path=os.getcwd()):
  37. default_keyfile = os.path.join(
  38. os.environ.get('HOME', 'D:/'), '.ssh', 'id_rsa')
  39. if 'id_rsa' in os.listdir(path):
  40. keyfile = os.path.join(path, 'id_rsa')
  41. elif os.path.isfile(default_keyfile):
  42. keyfile = default_keyfile
  43. else:
  44. keyfile = ''
  45. return keyfile
  46. def connect(self):
  47. try:
  48. transport = paramiko.Transport((self.host, self.port))
  49. except Exception:
  50. log.error(f"{self.host} is down!")
  51. exit(0)
  52. if self.password:
  53. transport.connect(username=self.username, password=self.password)
  54. elif self.keyfile:
  55. transport.connect(
  56. username=self.username,
  57. pkey=paramiko.RSAKey.from_private_key_file(self.keyfile))
  58. else:
  59. password = getpass.getpass("Password for %s@%s: " % (self.username, self.host))
  60. transport.connect(username=self.username, password=password)
  61. self._transport = transport
  62. log.info("Connected to %s as %s" % (self.host, self.username))
  63. def close(self):
  64. try:
  65. self._transport.close()
  66. except AttributeError:
  67. pass
  68. def run_cmd(self, command):
  69. ssh = paramiko.SSHClient()
  70. ssh._transport = self._transport
  71. stdin, stdout, stderr = ssh.exec_command(command)
  72. res = stdout.read().decode()
  73. error = stderr.read().decode()
  74. if error.strip():
  75. # return error
  76. raise Exception(error)
  77. else:
  78. return res
  79. def trans_file(self, localpath, remotepath, method=''):
  80. sftp = paramiko.SFTPClient.from_transport(self._transport)
  81. if method == 'put':
  82. sftp.put(localpath, remotepath)
  83. print("File %s has uploaded to %s" % (localpath, remotepath))
  84. elif method == 'get':
  85. sftp.get(remotepath, localpath)
  86. print("File %s has saved as %s" % (remotepath, localpath))
  87. else:
  88. print('usage: trans_file(localpath, remotepath, method="get/put"')
  89. def generter_md5(self, filepath):
  90. with open(filepath, 'rb') as f:
  91. data = f.read()
  92. return hashlib.md5(data).hexdigest()
  93. def load_directory(self):
  94. files = []
  95. for root, dirs, filenames in os.walk(self.dir):
  96. for f in filenames:
  97. filepath = os.path.join(root, f)
  98. files.append(filepath)
  99. return files
  100. def donwload_files(self, filepath, down_host):
  101. key = os.path.basename(filepath)
  102. download_script = """
  103. if [ -f {path}/{name} ];then
  104. echo "{path}/{name} exists!"
  105. rm -rf {path}/{name}
  106. else
  107. echo "{path}/{name} not exists!"
  108. fi
  109. wget -q http://{host}/{name} -O {path}/{name}
  110. """.format(path=self.dst_path, name=key, host=down_host)
  111. self.run_cmd(download_script)
  112. dst_md5 = self.run_cmd(
  113. "md5sum {path}/{name} | cut -d ' ' -f 1".format(path=self.dst_path, name=key)).strip()
  114. src_md5 = self.generter_md5(filepath)
  115. log.debug(f"remote ssh file md5:{dst_md5}, local file md5:{src_md5}")
  116. if dst_md5 != src_md5:
  117. log.error(f"md5校验失败, {self.dst_path}/{key} md5: {dst_md5}, local md5: {src_md5}")
  118. sys.exit(0)
  119. log.info(f"{self.host}, {self.dst_path}/{key} 下载完成,md5校验成功!")
  120. @staticmethod
  121. def find_ips(start, end):
  122. start = ip_address(start)
  123. end = ip_address(end) + 1
  124. result = []
  125. while start < end:
  126. result.append(str(start))
  127. start += 1
  128. return result
  129. def run(self) -> None:
  130. try:
  131. self.sem.acquire()
  132. self.connect()
  133. log.debug(f"files: {self.files}")
  134. if self.files:
  135. for filepath in self.files:
  136. self.donwload_files(filepath, self.down_host)
  137. log.info(f"directory: {self.dir}")
  138. if self.dir:
  139. log.info(f"run dir: {self.dir}")
  140. self.files = self.load_directory()
  141. for filepath in self.files:
  142. self.donwload_files(filepath, f"{self.down_host}/{self.dir}")
  143. if self.cmd:
  144. log.info(self.run_cmd(self.cmd))
  145. finally:
  146. self.sem.release()
  147. def __del__(self):
  148. self.close()
  149. def run(**kwargs):
  150. begin = perf_counter()
  151. ips = kwargs.get('ips', None)
  152. if not ips:
  153. log.error("Must specify hosts, use '-a' or '--addresses'")
  154. sys.exit(0)
  155. user, password = kwargs.get('user', None), kwargs.get('password', None)
  156. threads = []
  157. for ip in ips:
  158. log.debug(f"ip:{ip}")
  159. if ':' in ip:
  160. start, end = ip.split(':')
  161. for i in SSHConnection.find_ips(start, end):
  162. kwargs['host'] = i
  163. threads.append(SSHConnection(**kwargs))
  164. else:
  165. kwargs['host'] = ip
  166. threads.append(SSHConnection(**kwargs))
  167. for t in threads:
  168. t.start()
  169. for t in threads:
  170. t.join()
  171. end = perf_counter()
  172. print("Cost time:", end - begin)
  173. if __name__ == '__main__':
  174. # run()
  175. parser = argparse.ArgumentParser()
  176. parser.add_argument('--user', '-u', dest="user", action='store', help='Specify ssh username')
  177. parser.add_argument('--password', '-p', dest="password", action="store", help='Specify ssh password')
  178. parser.add_argument('--port', '-P', dest="port", action='store', default=22, type=int,
  179. help='Specify ssh port;default 22')
  180. parser.add_argument('--addresses', '-a', dest="ips", action='store', nargs='+', type=str, help='Specify ssh hosts')
  181. parser.add_argument('--cmd', '-c', dest="cmd", action='store', type=str, help="Specify cmd for execute")
  182. parser.add_argument('--sem', '-s', dest="sem", action='store', type=int, help="Number of threads; default 5",
  183. default=5)
  184. parser.add_argument('--down_host', dest='down_host', action='store', help="Download file server address")
  185. copy_group = parser.add_argument_group()
  186. copy_group.add_argument('--files', '-f', dest="files", action='store', nargs='+', type=str,
  187. help="Specify files use for copy")
  188. copy_group.add_argument('--dst_path', '-d', dest="dst_path", action="store", help="File storage directory")
  189. copy_group.add_argument('--dir', dest="dir", action='store', help='Specify storage files directory')
  190. args = parser.parse_args()
  191. print(args)
  192. sem = threading.Semaphore(args.sem)
  193. params = {
  194. "user": args.user,
  195. "password": args.password,
  196. "ips": args.ips,
  197. "port": args.port,
  198. "sem": sem,
  199. "files": args.files,
  200. "dst_path": args.dst_path,
  201. "cmd": args.cmd,
  202. "down_host": args.down_host,
  203. "dir": args.dir
  204. }
  205. run(**params)