一般而言,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