前言
在工作中我们经常遇到在不同的电脑之间传输文件,有时是同一网络下,有时是不同的网络之间传输文件,甚至是向客户传输文件。本文主要介绍常用的两种传输方式:FTP和SFTP,以及Python和shell脚本实现。
或许你会说,传文件嘛,U盘拷一下喽,QQ、微信、邮箱等都可以呀!
是的,方法有很多,但是如果每天都要你定时传一份文件给对方呢?你天天定时去插U盘拔U盘吗?
对于同一网络之间的电脑,我们可以在一台机器上设置一个共享目录,然后其他的电脑挂载这一个共享目录,这样就不用互传了。对于不同网络之间,或者与客户之间传输文件,平常工作中常用的还是通过FTP或SFTP
FTP与SFTP简介与区别
FTP是一种文件传输协议,用于文件之间的双向传输,同时又是一个应用程序,包括一个FTP服务器和多个FTP客户端,FTP客户端通过FTP协议向服务器上传文件或从服务器下载文件。
SFTP是一种安全的文件传送协议,是ssh内含协议,也就是说只要SSH服务器启动了,SFTP就可使用,不需要额外安装,它的默认端口和SSH一样为22。
SFTP通过使用加密/解密技术来保障传输文件的安全性,因此SFTP的传输效率比普通的FTP要低,但SFTP的安全性要比FTP高,因此SFTP通常用于报表、对账单等对安全性要求较高的场景。
废话不多说,想了解更多详情请自行百度Google。
脚本实现
如果是偶尔一次传输文件,可以使用命令行的方式交互的传输文件,但如果作为定时任务或者经常性的工作就要写脚本自动传输文件。接下来,介绍在shell和Python中如何实现FTP和SFTP的文件传输。
FTP
交互式的互传文件首先要登录FTP,登录方式有两种:
1、使用命令 ftp host port
2、先使用命令 ftp ,然后使用命令:open host port
然后根据提示输入用户名和密码。成功登录后就可以传输文件了,传输文件中常用的命令有:
cd remote-directory :切换进入远程工作目录
lcd local-directory:切换进入本地工作目录
binary:设置为二进制传输
get remote-file [local-file]:下载远程文件到本地
mkdir directory-name :在远程机器上创建文件夹
put local-file [remote-file] : 上传本地文件到远程机器
pwd :打印远程机器的当前工作目录
rmdir directory-name : 删除远程机器上的目录
还有许多其他的命令,可以使用info ftp 查看更多命令详情。
如果作为定时任务,每天都要上传或下载一份文件,那么交互式的方式就不行,可以使用自动上传下载的方式,shell脚本如下:
ftp -v -n<<EOF
open host port
user abc ***
binary
lcd /service/data
prompt
mkdir 20181015
cd 20181015
put 20181015.zip
close
bye
EOF
其中-v:程序运行时显示详细的处理信息,-n:关闭自动登录功能,prompt:关闭交互模式。这样可以将上述脚本写入.sh文件,将日期作为变量,每天执行.sh文件就行了,如下:
#!/bin/bash
TODAY=$(date +%Y%m%d)
TODAY_NAME=$(date +%m%d)
ftp -v -n<<EOF
open host port
user abc ***
binary
lcd /service/data
prompt
mkdir ${TODAY}
cd ${TODAY}
put ${TODAY_NAME}.zip
close
bye
EOF
上面介绍的是shell脚本,当然也可以通过编写Python脚本实现ftp传输文件。ftplib模块中的FTP()类可以实现此功能。下面介绍一些FTP()类中的方法。
- connect(host=‘’, port=0, timeout=-999)
链接ftp服务器主机 - login(user = ‘’, passwd = ‘’, acct = ‘’)
使用用户名和密码进行登录,acct是登录成功后访问资源所需的补充密码 - set_debuglevel(level)
设置调试级别,level有三个级别:
0:没有调试输出(默认)
1:打印命令和响应,但不打印正文等
2:还打印读取和发送的原始行中CR/LF前的内容 - getwelcome()
打印ftp服务器的欢迎信息 - cwd(dirname)
进入目录 - mkd(dirname)
创建目录,返回目录的完整路径名 - rename(fromname, toname)
重命名文件 - nlst(*args)
返回给定目录下的文件列表,默认返回当前目录文件列表 - quit()
离开并关闭连接 - retrbinary(cmd, callback, blocksize=8192, rest=None)
以二进制模式检索数据,并为你创建一个新的端口
cmd :RETR命令
callback :在读取的每个数据块上可调用的单个参数,即读取到的数据的回调函数
blocksize :一次从套接字读取的最大字节数。[默认值:8192 ] - storbinary(cmd, fp, blocksize=8192, callback=None, rest=None)
以二进制模式存储文件。为您创建了一个新的端口。
cmd :STOR命令
fp :一个具有read(num_bytes)方法的类文件对象
blocksize :一次从fp读取并发送给连接的数据大小[默认值:8192 ]
了解了FTP()类的方法的之后,我们可以封装一个方便自己使用的类,这个类主要实现上传和下载文件或文件夹,这样做的好处是在使用时快速实现,减少代码量,具体代码如下:
# -*- coding: utf-8 -*-
from ftplib import FTP
import os
class FTPUtil():
DEFAULT_TIME_OUT = 30
def __init__(self, username, password, server, port=21):
self.username = username
self.pasword = password
self.server = server
self.port = port
self.ftp = self._connect()
def _connect(self):
ftp_server = self.server # FTP server ip address
username = self.username
password = self.pasword
timeout = self.DEFAULT_TIME_OUT
port = self.port
ftp = FTP()
ftp.set_debuglevel(1)
ftp.set_pasv(False)
ftp.connect(ftp_server, port, timeout) # connect to FTP server
ftp.login(username, password)
print ftp.getwelcome() # can display FTP server welcome message.
return ftp
def _makeRemotePath(self, target_dir):
self.ftp.cwd('/')
target_dir.strip('/')
dir_items = target_dir.split('/')
for item in dir_items:
try:
self.ftp.cwd(item)
except:
self.ftp.mkd(item)
self.ftp.cwd(item)
def _makeLocalPath(self, target_dir):
if not os.path.isdir(target_dir):
os.makedirs(target_dir)
def downloadfile_from_FTP(self, remote_file, local_dir):
bufsize = 1024 # set buffer size
file_name = remote_file.split('/')[-1]
self._makeLocalPath(local_dir)
fp = open(os.path.join(local_dir,file_name), "wb")
# start to download file :FTP server --> local
self.ftp.retrbinary('RETR %s' % remote_file, fp.write, bufsize)
fp.close() # close connect
print "download finished!"
def uploadfile_to_FTP(self, local_file, remote_dir):
bufsize = 1024
file_name = local_file.split('/')[-1]
self._makeRemotePath(remote_dir)
fp = open(local_file, 'rb')
# start to upload file :local --> FTP server
self.ftp.storbinary('STOR ' + file_name, fp, bufsize)
fp.close() # close connect
print "upload finished!"
def downloaddir_from_FTP(self, remote_dir, local_dir):
#self._makeLocalPath(local_dir)
self.ftp.cwd(remote_dir)
remotenames = self.ftp.nlst()
for remote_file in remotenames:
self.downloadfile_from_FTP(remote_file,local_dir)
def uploaddir_to_FTP(self, local_dir, remote_dir):
#self._makeRemotePath(remote_dir)
for filename in os.listdir(local_dir):
local_file = os.path.join(local_dir,filename)
self.uploadfile_to_FTP(local_file, remote_dir)
def rename_filename_from_FTP(self, path, old_name, new_name):
self.ftp.cwd(path)
self.ftp.rename(old_name, new_name)
print "rename finished!"
def get_filesname_from_FTP(self, path):
self.ftp.cwd(path)
return self.ftp.nlst()
def close(self):
self.ftp.quit()
if __name__ == "__main__":
ftp_util = FTPUtil("abc", "***", "61.**.**")
print ftp_util.get_filesname_from_FTP("/")
ftp_util.close()
在这个封装的FTPUTil()类中,初始化参数有ftp服务器地址,用户名和密码,端口默认是21,而初始化类后即登录ftp,默认登录超时时间的30秒。然后就可以调用传输文件或文件夹的方法,传输文件夹的方法是遍历文件夹下面的文件,然后传输单个文件。
有时,我们传输文件的目标路径文件夹可能不存在,因此传输文件前要先检查目录是否存在,_makeRemotePath()和_makeLocalPath()方法分别检查远程目录和本地目录,如果不存在则创建,其中_makeRemotePath()方法是将路径分割,尝试依次进入,如果进入失败,则说明此目录不存在,则创建。调用此方法后,当前工作目录就是目标目录。
downloadfile_from_FTP()和uploadfile_to_FTP()分别实现下载文件和上传文件,而参数都是源文件和目标目录,这里我们默认传输文件后文件名保持不变。downloaddir_from_FTP()和uploaddir_to_FTP()分别实现下载和长传文件夹。
当然,也可以根据自己需要实现其他的方法,比如rename_filename_from_FTP(),在最后不要忘了关闭ftp链接(close())。
SFTP
SFTP也可以以交互的方式登录,登录命令为 sftp -P port user@host,如下图所示:
登录成功后就可以传输文件,常用的命令和ftp差不多,这里不再赘述,可以通过info sftp查看更多命令详情。但是如果要自动输入密码,就要借助其他软件,可用的有lftp、sshpass等。这里使用sshpass。具体的shell脚本见下:
#!/bin/bash
TODAY=$(date +%Y%m%d)
#TODAY=20180806
TODAY_NAME=$(date +%m%d)
#TODAY_NAME=0806
HOST=61.**.**
PORT=22
USER=abc
PASSWD=****
export SSHPASS=$PASSWD
sshpass -e sftp -oBatchMode=no -oport=22 $USER@$HOST << !
lcd /home/songzp
cd upload
mkdir ${TODAY}
cd ${TODAY}
put ${TODAY_NAME}.zip
close
bye
!
其中-e 的意思是密码通过环境变量SSHPASS获得,变量名称必须是“SSHPASS”。当然,密码也可以通过文件或其他方式获得。
在Python环境下,可以使用paramiko模块实现sftp传输文件。同样的方法,我们封装一个方便自己使用的工具类SFTPUtils()类,具体代码如下:
# coding=utf-8
'''
Created on 2018年4月26日
@author: Administrator
'''
import os
import paramiko
class SftpUtils(object):
def __init__(self, host, port, user, password):
self._host = host
self._port = port
self._user = user
self._password = password
self.SFTP = self._connect()
def _connect(self):
u"""
链接SFTP
"""
try:
transport = paramiko.Transport((self._host,self._port))
transport.connect(username=self._user,password=self._password)
SFTP = paramiko.SFTPClient.from_transport(transport)
print u"链接成功..."
return SFTP
except Exception as e:
print u"连接失败..%s" % e
def _makeRemotePath(self, target):
u"""
创建目标路径
说明: 目标路径不存在则依次创建路径目录
"""
# 切换根目录
self.SFTP.chdir('/')
# 分割目标目录为目录单元集合
target.strip('/')
data = target.split('/')
# 进入目标目录, 目录不存在则创建
for item in data:
try:
self.SFTP.chdir(item)
print u'要上传的目录已经存在,选择性进入合并:' + item
except:
self.SFTP.mkdir(item)
self.SFTP.chdir(item)
print u'要上传的目录不存在,创建目录:' + item
def _makeLocalPath(self, target_dir):
if not os.path.isdir(target_dir):
os.makedirs(target_dir)
def upload_file(self, localfile, remotedir):
u"""
传输单个文件
"""
self._makeRemotePath(remotedir)
filename = localfile.split('/')[-1]
# 上传文件
try:
self.SFTP.put(localfile, filename)
print u'%s 上传成功:' % filename
except Exception as e:
print u'%s 上传失败:%s' % (filename, str(e))
def download_file(self, remotefile, localdir):
u"""
下载单个文件
"""
filename = remotefile.split('/')[-1]
self._makeLocalPath(localdir)
# 下载文件
try:
self.SFTP.get(remotefile, os.path.join(localdir, filename))
except Exception as e:
print u'%s 下载失败:%s' % (filename, str(e))
def upload_dir(self, localdir, remotedir):
for filename in os.listdir(localdir):
localfile = os.path.join(localdir, filename)
self.upload_file(localfile, remotedir)
def download_dir(self, remotedir, localdir):
self.SFTP.chdir(remotedir)
for remote_file in self.SFTP.listdir(remotedir):
self.download_file(remote_file, localdir)
def close(self):
self.SFTP.close()
if __name__ == '__main__':
sftp = SftpUtils("61.***.**",22,"abc","***")
sftp.upload_file("/home/songzp/20181016.zip","/upload/20181016/20181016.zip")
sftp.close()
SFTP通过Transport()类登录,host和port可以作为元组传入,就像代码中的那样,也可以组合为“host:port”字符串传入,登录SFTP后,下载和上传文件直接通过get()方法和put()方法轻松实现,但是这两个方法的目标路径参数必须是文件,不能是一个目录。
有时会遇到需要通过TLS/SSL加密的方式连接FTP,可以使用lftp,不过Python的ftplib模块中的FTP_TLS()类可以实现,该类继承自FTP()类,因此使用方法一样。关于FTP与SFTP的内容还有很多,本文本着会使用的初衷,简单介绍了传输文件的方法,更多更详细的知识还需进一步探索实践。