一般而言,FTP传输过程当中,客户端在完成帐户认证后,须要指定具体的文件路径方能下载或删除服务器端的文件。可是在使用命令行指令去操做ftp数据时,若是每次都要输入完整的路径就太麻烦了,并且若是想要同时下载多个文件还需逐个执行下载指令,那有什么方法能够经过正则表达式去完成模糊匹配和批量下载呢?本文就来介绍一下FTP数据传输的经常使用操做及正则匹配的实现方法。html
在介绍ftp数据传输以前,简单介绍下经常使用的几款ftp client:linux
ftp
lftp
(支持ftp, http, https, sftp, fish, torrent, fxp, ...)sftp
(Secure File Transfer Protocol)ftp
是最基本的ftp客户端,高效但不安全,数据传输过程当中使用明文,容易被截获和篡改。lftp
是很是强大的一款文件传输工具,支持多种文件传输协议,功能强大,支持递归镜像整个目录及断点续传等,也是本文采用的ftp客户端。sftp
是ssh
的一部分,支持加密传输,与ftp
语法基本一致,很是安全可是传输效率较低。最后的FileZilla
是一款图形化软件,在windows操做系统中使用较多。正则表达式
本文主要介绍如下四个经常使用的ftp操做shell
lftp
指令的语法以下:windows
lftp [-d] [-e cmd] [-p port] [-u user[,pass]] [site]
lftp -f script_file
lftp -c commands
lftp --version
lftp --help
复制代码
lftp
的帮助信息中能够看到全部能够执行的指令。安全
$ lftp -u "username,password" ftp://host.ip
lftp username@host:~> help
!<shell-command> (commands)
alias [<name> [<value>]] attach [PID]
bookmark [SUBCMD] cache [SUBCMD]
cat [-b] <files> cd <rdir>
chmod [OPTS] mode file... close [-a]
[re]cls [opts] [path/][pattern] debug [OPTS] [<level>|off]
du [options] <dirs> edit [OPTS] <file>
exit [<code>|bg] get [OPTS] <rfile> [-o <lfile>]
glob [OPTS] <cmd> <args> help [<cmd>]
history -w file|-r file|-c|-l [cnt] jobs [-v] [<job_no...>]
kill all|<job_no> lcd <ldir>
lftp [OPTS] <site> ln [-s] <file1> <file2>
ls [<args>] mget [OPTS] <files>
mirror [OPTS] [remote [local]] mkdir [OPTS] <dirs>
module name [args] more <files>
mput [OPTS] <files> mrm <files>
mv <file1> <file2> mmv [OPTS] <files> <target-dir>
[re]nlist [<args>] open [OPTS] <site>
pget [OPTS] <rfile> [-o <lfile>] put [OPTS] <lfile> [-o <rfile>]
pwd [-p] queue [OPTS] [<cmd>]
quote <cmd> repeat [OPTS] [delay] [command]
rm [-r] [-f] <files> rmdir [-f] <dirs>
scache [<session_no>] set [OPT] [<var> [<val>]]
site <site-cmd> source <file>
torrent [OPTS] <file|URL>... user <user|URL> [<pass>]
wait [<jobno>] zcat <files>
zmore <files>
lftp username@host:~>
复制代码
基于安全考虑,绝大多数的ftp server都会设置帐户密码,那么使用lftp
该如何完成认证呢?其中在上面的示例中已经给出答案了,就是经过参数-u
指定。bash
lftp -u "$ftp_user,$ftp_pass"
复制代码
若是将密码存储在某个文件~/local/etc/ftp_pass
,那么能够在脚本中使用一个函数进行获取。服务器
get_ftp_pass()
{
pass_file=$HOME/local/etc/ftp_pass
[ -f $pass_file ] && ftp_pass=$(cat $pass_file)
test -z "$ftp_pass" \
&& read -rs -p "Please input password of your FTP user $ftp_user: " ftp_pass
}
复制代码
若是彻底使用lftp
完成ftp传输的各个功能,那么能够在shell
脚本中使用如下模板完成各个指令操做:session
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
COMMAND1 [Args1]
COMMAND2 [Args2]
EOF
复制代码
因为文件上传是将本地文件传输至ftp server,那么一般状况不须要正则匹配,本地文件选择经过shell的tab自动补全便可作到。ssh
使用lftp
的put $file -o $remotefile
可将本地文件$file
传输至ftp server并重命名为$remotefile
,-o
参数用于指定server端的文件名。
ftp_put_file()
{
get_ftp_pass
prefix=$(date '+%Y%m%d%H%M%S-')
remotefile=${prefix}${file##*/}
subdir=$(date '+%Y%m%d')
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
mkdir -p -f $subdir
cd $subdir && put ${file##*/} -o ${remotefile}
EOF
}
复制代码
如上代码所示,当咱们想要将文件传输至服务端子目录时,须要经过mkdir
和cd
指令完成目录的建立和切换。在本例中,咱们将每次上传的文件都放置在了以当天日期命名所在的文件夹,并给原有文件名加上了时间戳前缀。
此处须要普及两个知识点:
变量切割
#
删除变量左侧的最短匹配;##
删除变量左侧的最长匹配
%
删除变量右侧的最短匹配;%%
删除变量右侧的最长匹配${file##*/} 以
/
为分隔符,删除最后一个/
往左的全部字符,此处用于获取文件名${file%/*} 以
/
为分隔符,删除最后一个/
往右的全部字符,此处用于获取目录
咱们一般在脚本中使用${0##*/}
获取当前执行指令的文件名。
<<-EOF
语法man bash
[n]<<[-]word here-document delimiter
......
If the redirection operator is
<<-
, then all leading tab characters are stripped from input lines and the line containing delimiter. This allows here-documents within shell scripts to be indented in a natural fashion.
简单点说,<<-EOF
中的链接符-
保证下面语句中的每行开头的tab
分隔符会被忽略,但又能够保证代码的天然美观。若是下面语句中开头的tab
键是空格替换的,那么有可能会报语法错误,这也是须要注意的。
文件下载是本文重点,由于咱们将完成ftp服务器端文件的模糊匹配下载。在讲述模糊匹配下载以前,先讲讲使用lftp
实现下载的方法。
ftp_get_file()
{
get_ftp_pass
if test "${file%/*}" = "${file}"; then
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
set xfer:clobber on
get ${file}
EOF
else
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
set xfer:clobber on
cd ${file%/*} && get ${file##*/}
EOF
fi
}
复制代码
以上实现中,分带有子目录和不带子目录两种状况,指令set xfer:clobber on
是为了解决重复下载时提示文件已存在的问题。这种方法简单,可是每次只能下载一个确切名称的文件。
好了,接下来介绍可以实现模糊匹配及批量下载的方法,思路其实很简单:
curl
指令下载指定ftp
目录,获得指定目录的文件列表信息awk
, grep
指令完成正则模糊匹配,获取真实文件路径wget
指令批量下载匹配到的文件根据这个思路编写代码以下:
ftp_get_file()
{
get_ftp_pass
# get subdir and regex pattern of filenames
result=$(echo "$file" |grep "/")
if [ "x$result" != "x" ]; then
# split file to directory and re pattern of files
subdir=${file%/*}/
re_pattern=${file##*/}
else
subdir="/"
re_pattern=$file
fi
# 1. curl get file list
files=$(curl -s -u ${ftp_user}:${ftp_pass} ${ftp_host}/${subdir})
[ $? -eq 67 ] && echo "curl: password error!" && exit 2
# 2. grep with regex to get files which need download
files=$(echo "$files" |awk '{print $4}' |grep "${re_pattern}")
[ "x$files" = "x" ] && echo "Not Found Files" && exit 3
file_nums=$(echo "$files" |wc -l)
[ ! $file_nums -eq 1 ] && {
files=$(echo "$files" |xargs)
files="{${files//\ /,}}"
}
# 3. wget files
eval wget --ftp-user=${ftp_user} --ftp-password=${ftp_pass} ${ftp_host}/${subdir}${files} -nv
}
复制代码
首先从带有模糊匹配的文件名中分隔出远程目录及文件名的正则表达式,而后根据预约的思路逐步完成文件匹配及下载。这里须要注意的几个问题有:
curl
及wget
有各自的认证参数:
curl -u %{ftp_user}:${ftp_pass}
wget --ftp-user=${ftp_user} --ftp_password=${ftp_pass}
{}
包含在一块儿,因此代码中使用了wc -l
统计匹配到的文件数$files
,在用echo
打印时需加上双引号"$files"
,不然换行符会自动变为空格${files//\ /,}
就是将$files
变量中的全部空格替换成了逗号
//
替换全部匹配项/
仅仅替换第一个匹配项对于文件删除,因为使用较少,因此没有对其实现模糊匹配,固然想要实现也是能够的。这里仅给出最基本的删除方式:
ftp_del_file()
{
get_ftp_pass
if test "${file%/*}" = "${file}"; then
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
rm -rf ${file}
EOF
else
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
cd ${file%/*} && rm -rf ${file##*/}
EOF
fi
}
复制代码
到此,常见的ftp操做都已经介绍完了。
完整代码fctl
以下:
#!/bin/bash
cmd="${0##*/}"
ftp_host="ftp://127.0.0.1"
test -z "$ftp_user" && ftp_user="${USER}"
#usage()
#{
# cat <<-EOF >&2
# Usage: fput <file>
# fget <file/dir>
# fdel <file/dir>
# EOF
#}
get_ftp_pass()
{
pass_file=$HOME/local/etc/ftp_pass
[ -f $pass_file ] && ftp_pass=$(cat $pass_file)
test -z "$ftp_pass" \
&& read -rs -p "Please input password of your FTP user $ftp_user: " ftp_pass
}
ftp_put_file()
{
get_ftp_pass
prefix=$(date '+%Y%m%d%H%M%S-')
remotefile=${prefix}${file##*/}
subdir=$(date '+%Y%m%d')
if test -z "$subdir"; then
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
put ${file##*/} -o ${remotefile}
EOF
else
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
mkdir -p -f $subdir
cd $subdir && put ${file##*/} -o ${remotefile}
EOF
fi
}
ftp_get_file()
{
get_ftp_pass
result=$(echo "$file" |grep "/")
if [ "x$result" != "x" ]; then
# split file to directory and re pattern of files
subdir=${file%/*}/
re_pattern=${file##*/}
else
subdir="/"
re_pattern=$file
fi
# 1. curl get file list
files=$(curl -s -u ${ftp_user}:${ftp_pass} ${ftp_host}/${subdir})
[ $? -eq 67 ] && echo "curl: password error!" && exit 2
# 2. grep with regex to get files which need download
files=$(echo "$files" |awk '{print $4}' |grep "${re_pattern}")
[ "x$files" = "x" ] && echo "Not Found Files" && exit 3
file_nums=$(echo "$files" |wc -l)
[ ! $file_nums -eq 1 ] && {
files=$(echo "$files" |xargs)
files="{${files//\ /,}}"
}
# 3. wget files
eval wget --ftp-user=${ftp_user} --ftp-password=${ftp_pass} ${ftp_host}/${subdir}${files} -nv
}
ftp_del_file()
{
get_ftp_pass
if test "${file%/*}" = "${file}"; then
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
rm -rf ${file}
EOF
else
lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF
cd ${file%/*} && rm -rf ${file##*/}
EOF
fi
}
case "$cmd" in
"fput")
file="${1:?missing arg 1 to specify file path!!!}"
cd "$(dirname $(readlink -f $file))" && ftp_put_file ;;
"fget")
file="${1:?missing arg 1 to specify file path!!!}"
ftp_get_file ;;
"fdel")
file="${1:?missing arg 1 to specify file path!!!}"
ftp_del_file ;;
esac
复制代码
使用ln -s
建立fput
,fget
,fdel
三个软连接,即可经过这三个别名完成对应的上传、下载和删除操做了。
fput test # test文件将存放至server当天目录,并冠以时间戳为文件名前缀
fput ~/bin/fget # fget文件将存放至server当天目录,并冠以时间戳为文件名前缀
复制代码
fget 20190902/ # 获取服务器端20190902目录下全部文件
fget 20190902/2019 # 获取服务器端20190902目录下包含2019字符的全部文件
fget test # 获取服务器端根目录下包含test子串的全部文件
fget te.*st # 获取服务器端根目录下符合匹配符的全部文件,如test,teast,teost,teeest
复制代码
fdel test # 删除服务器端根目录名为test的文件
fdel docs/test # 删除服务器端docs目录下名为test的文件
复制代码
文章首发于www.litreily.top