使用正则模糊匹配的ftp文件传输

使用正则模糊匹配的ftp文件传输通常而言,FTP传输过程中,客户端在完成账户认证后,需要指定具体的文件路径方能下载或删除服务器端的文件。但是在使用命令行指令去操作ftp数据时,如果每次都要输入完整的路径就太麻烦了,而且如果想要同时下载多个文件还需逐个执行下载指令,那有什么方法可以通过正则表达式去完成模糊匹配和…

通常而言,FTP传输过程中,客户端在完成账户认证后,需要指定具体的文件路径方能下载或删除服务器端的文件。但是在使用命令行指令去操作ftp数据时,如果每次都要输入完整的路径就太麻烦了,而且如果想要同时下载多个文件还需逐个执行下载指令,那有什么方法可以通过正则表达式去完成模糊匹配和批量下载呢?本文就来介绍一下FTP数据传输的常用操作及正则匹配的实现方法。

ftp diagram

常用的ftp客户端

在介绍ftp数据传输之前,简单介绍下常用的几款ftp client:

  • ftp
  • lftp(支持ftp, http, https, sftp, fish, torrent, fxp, …)
  • sftp(Secure File Transfer Protocol)
  • FileZilla(图形化软件,支持ftp, ftps, sftp)

ftp是最基本的ftp客户端,高效但不安全,数据传输过程中使用明文,容易被截获和篡改。lftp是非常强大的一款文件传输工具,支持多种文件传输协议,功能强大,支持递归镜像整个目录及断点续传等,也是本文采用的ftp客户端。sftpssh的一部分,支持加密传输,与ftp语法基本一致,非常安全但是传输效率较低。最后的FileZilla是一款图形化软件,在windows操作系统中使用较多。

ftp常用操作

本文主要介绍以下四个常用的ftp操作

  • 账户认证
  • 文件上传
  • 文件下载(用到正则模糊匹配)
  • 文件删除

lftp指令的语法如下:

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指定。

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脚本中使用以下模板完成各个指令操作:

lftp -u "$ftp_user,$ftp_pass" $ftp_host <<-EOF COMMAND1 [Args1] COMMAND2 [Args2] EOF

文件上传

由于文件上传是将本地文件传输至ftp server,那么通常情况不需要正则匹配,本地文件选择通过shell的tab自动补全即可做到。

使用lftpput $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
}

如上代码所示,当我们想要将文件传输至服务端子目录时,需要通过mkdircd指令完成目录的创建和切换。在本例中,我们将每次上传的文件都放置在了以当天日期命名所在的文件夹,并给原有文件名加上了时间戳前缀。

此处需要普及两个知识点:

  • shell中的变量切割

变量切割

# 删除变量左侧的最短匹配;## 删除变量左侧的最长匹配

% 删除变量右侧的最短匹配;%%删除变量右侧的最长匹配

${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是为了解决重复下载时提示文件已存在的问题。这种方法简单,但是每次只能下载一个确切名称的文件。

好了,接下来介绍能够实现模糊匹配及批量下载的方法,思路其实很简单:

  1. 从给定的文件路径中解析出目录以及符合正则表达式的文件名
  2. 使用curl指令下载指定ftp目录,得到指定目录的文件列表信息
  3. 对列表信息使用awk, grep指令完成正则模糊匹配,获取真实文件路径
  4. 使用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
}

首先从带有模糊匹配的文件名中分隔出远程目录及文件名的正则表达式,然后根据预定的思路逐步完成文件匹配及下载。这里需要注意的几个问题有:

  1. curlwget有各自的认证参数:
    • curl -u %{ftp_user}:${ftp_pass}
    • wget --ftp-user=${ftp_user} --ftp_password=${ftp_pass}
  2. 当只匹配到一个文件时,文件名不能使用{}包含在一起,所以代码中使用了wc -l统计匹配到的文件数
  3. 对于包含换行符的变量$files,在用echo打印时需加上双引号"$files",否则换行符会自动变为空格
  4. shell中的变量可以进行字符替换,${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
fput test	# test文件将存放至server当天目录,并冠以时间戳为文件名前缀
fput ~/bin/fget  # fget文件将存放至server当天目录,并冠以时间戳为文件名前缀
  • fget
fget 20190902/	# 获取服务器端20190902目录下所有文件
fget 20190902/2019	# 获取服务器端20190902目录下包含2019字符的所有文件
fget test	# 获取服务器端根目录下包含test子串的所有文件
fget te.*st # 获取服务器端根目录下符合匹配符的所有文件,如test,teast,teost,teeest
  • fdel
fdel test	# 删除服务器端根目录名为test的文件
fdel docs/test	# 删除服务器端docs目录下名为test的文件

参考资料

文章首发于www.litreily.top

今天的文章使用正则模糊匹配的ftp文件传输分享到此就结束了,感谢您的阅读。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/16319.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注