使用正則模糊匹配的ftp文件傳輸

一般而言,FTP傳輸過程當中,客戶端在完成帳戶認證後,須要指定具體的文件路徑方能下載或刪除服務器端的文件。可是在使用命令行指令去操做ftp數據時,若是每次都要輸入完整的路徑就太麻煩了,並且若是想要同時下載多個文件還需逐個執行下載指令,那有什麼方法能夠經過正則表達式去完成模糊匹配和批量下載呢?本文就來介紹一下FTP數據傳輸的經常使用操做及正則匹配的實現方法。html

ftp diagram

經常使用的ftp客戶端

在介紹ftp數據傳輸以前,簡單介紹下經常使用的幾款ftp client:linux

  • 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操做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

使用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

相關文章
相關標籤/搜索