本文將講解一些比較簡單的 zsh 腳本實例。git
功能:github
將一個目錄及它下邊的全部目錄複製到另外一個目錄中(即建立同名目錄),但不復制目錄下的其餘類型文件。數組
例子:微信
src 的目錄結構: src ├── a ├── b │ ├── 1.txt │ └── 2 │ └── 3.txt ├── c.txt ├── d ├── e f │ └── g │ └── 4.txt └── g h -> e f 要構造一個 dst 目錄,只包含 src 下的目錄,內容以下: dst └── src ├── a ├── b │ └── 2 ├── d └── e f └── g
思路:併發
**/*(/)
匹配。mkdir -p
在 dst 目錄中建立對應的目錄。# 參數 1:src 目錄 # 參數 2:待建立的 dst 目錄 #!/bin/zsh for i ($1/**/*(/)) { # -p 參數是遞歸建立目錄,這樣不用考慮目錄的建立順序 mkdir -p $2/$i }
功能:socket
須要當前目錄下有一些 .txt 和 .txt.md5sum 的文件,須要尋找出沒有對應的 .md5sum 文件的 .txt 文件。(實際的場景是尋找已經下載完成的文件,未下載完的文件都對應某個帶後綴的文件。)ide
例子:函數
當前目錄的全部文件: aa.txt bb.txt bb.txt.md5sum cc dd.txt cc dd.txt.md5sum ee ff.txt.md5sum gg.txt hh ii.txt 須要找出沒有對應 .md5sum 的 .txt 文件: aa.txt gg.txt hh ii.txt
思路:工具
實現:ui
#!/bin/zsh all_files=(*) bad_files=(*.md5sum) bad_files+=(${bad_files/.md5sum}) # 數組差集操做 echo ${all_files:|bad_files}
功能:
用形如 sed 命令的用法批量重命名文件。
例子:
# 實現 renamex 命令,接受的第一個參數爲 sed 的主體參數,其他參數是文件列表 # 效果是根據 sed 對文件名的修改重命名這些文件 % tree . ├── aaa_aaa.txt ├── aaa.txt ├── ccc.txt └── xxx ├── aaa bbb.txt └── bbb ccc.txt % renamex s/aaa/bbb/g **/* 'aaa_aaa.txt' -> 'bbb_bbb.txt' 'aaa.txt' -> 'bbb.txt' 'xxx/aaa bbb.txt' -> 'xxx/bbb bbb.txt' % tree . ├── bbb_bbb.txt ├── bbb.txt ├── ccc.txt └── xxx ├── bbb bbb.txt └── bbb ccc.txt
思路:
實現:
#!/bin/zsh (($+2)) || { echo 'Usage: renamex s/aaa/bbb/g *.txt' return } for name ($*[2,-1]) { local new_name="$(echo $name | sed $1)" [[ $name == $new_name ]] && continue mv -v $name $new_name }
功能:
刪除當前目錄以及子目錄下全部的重複文件(根據 md5 判斷,不是很嚴謹)。
思路:
實現:
#!/bin/zsh # D 是包含以 . 開頭的隱藏文件 local files=("${(f)$(md5sum **/*(.D))}") local files_to_delete=() local -A md5s for i ($files) { # 取前 32 位,即 md5 的長度 local md5=$i[1,32] if (($+md5s[$md5])) { # 取 35 位以後的內容,即文件路徑,md5 後邊有兩個空格 files_to_delete+=($i[35,-1]) } else { md5s[$md5]=1 } } (($#files_to_delete)) && rm -v $files_to_delete
功能:
轉換 100 之內的漢字數字爲阿拉伯數字,如六十八轉換成 68。
思路:
實現:
#!/bin/zsh local -A table=( 零 0 一 1 二 2 三 3 四 4 五 5 六 6 七 7 八 8 九 9 ) local result if [[ $1 == 十 ]] { result=一零 } elif [[ $1 == 十* ]] { result=${1/十/一} } elif [[ $1 == *十 ]] { result=${1/十/零} } elif [[ $1 == *十* ]] { result=${1/十} } else { result=$1 } for i ({1..$#result}) { result[i]=$table[$result[i]] if [[ -z $result[i] ]] { echo error return 1 } } echo $result 運行結果: % ./convert 一 1 % ./convert 十 10 % ./convert 十五 15 % ./convert 二十 20 % ./convert 五十六 56 % ./convert 一百 error
功能:
見下邊例子。
例子:
當前目錄有以下文件: Zsh-開發指南(第一篇-變量和語句).md Zsh-開發指南(第七篇-數值計算).md Zsh-開發指南(第三篇-字符串處理之轉義字符和格式化輸出).md Zsh-開發指南(第九篇-函數和腳本).md Zsh-開發指南(第二篇-字符串處理之經常使用操做).md Zsh-開發指南(第五篇-數組).md Zsh-開發指南(第八篇-變量修飾語).md Zsh-開發指南(第六篇-哈希表).md Zsh-開發指南(第十一篇-變量的進階內容).md Zsh-開發指南(第十七篇-使用-socket-文件和-TCP-實現進程間通訊).md Zsh-開發指南(第十三篇-管道和重定向).md Zsh-開發指南(第十九篇-腳本實例講解).md Zsh-開發指南(第十二篇-[[-]]-的用法).md Zsh-開發指南(第十五篇-進程與做業控制).md Zsh-開發指南(第十八篇-更多內置模塊的用法).md Zsh-開發指南(第十六篇-alias-和-eval-的用法).md Zsh-開發指南(第十四篇-文件讀寫).md Zsh-開發指南(第十篇-文件查找和批量處理).md Zsh-開發指南(第四篇-字符串處理之通配符).md 須要重命名成這樣: 01_Zsh-開發指南(第一篇-變量和語句).md 02_Zsh-開發指南(第二篇-字符串處理之經常使用操做).md 03_Zsh-開發指南(第三篇-字符串處理之轉義字符和格式化輸出).md 04_Zsh-開發指南(第四篇-字符串處理之通配符).md 05_Zsh-開發指南(第五篇-數組).md 06_Zsh-開發指南(第六篇-哈希表).md 07_Zsh-開發指南(第七篇-數值計算).md 08_Zsh-開發指南(第八篇-變量修飾語).md 09_Zsh-開發指南(第九篇-函數和腳本).md 10_Zsh-開發指南(第十篇-文件查找和批量處理).md 11_Zsh-開發指南(第十一篇-變量的進階內容).md 12_Zsh-開發指南(第十二篇-[[-]]-的用法).md 13_Zsh-開發指南(第十三篇-管道和重定向).md 14_Zsh-開發指南(第十四篇-文件讀寫).md 15_Zsh-開發指南(第十五篇-進程與做業控制).md 16_Zsh-開發指南(第十六篇-alias-和-eval-的用法).md 17_Zsh-開發指南(第十七篇-使用-socket-文件和-TCP-實現進程間通訊).md 18_Zsh-開發指南(第十八篇-更多內置模塊的用法).md 19_Zsh-開發指南(第十九篇-腳本實例講解).md
思路:
實現:
#!/bin/zsh # 轉換數字的邏輯和上一個實例同樣 local -A table=( 零 0 一 1 二 2 三 3 四 4 五 5 六 6 七 7 八 8 九 9 ) convert() { local result if [[ $1 == 十 ]] { result=一零 } elif [[ $1 == 十* ]] { result=${1/十/一} } elif [[ $1 == *十 ]] { result=${1/十/零} } elif [[ $1 == *十* ]] { result=${1/十} } else { result=$1 } for i ({1..$#result}) { result[i]=$table[$result[i]] if [[ -z $result[i] ]] { echo error return 1 } } echo $result } for i (Zsh*.md) { # -Z 2 是爲了在前邊補全一個 0 # 把文件名「第」以前和「篇」以後的所有去除 local -Z 2 num=$(convert ${${i#*第}%篇*}) mv -v $i ${num}_$i }
功能:
Linux 下經常使用的壓縮、歸檔格式衆多,參數各異,寫一個用法統一的壓縮解壓工具,用於建立、解壓 .zip
.7z
.tar
.tgz
.tbz2
.txz
.tar.gz
.tar.bz2
.tar.xz
.cpio
.ar
.gz
.bz2
.xz
等文件。(相似 atool
,但 atool
好久沒更新了,一些新的格式不支持,無法定製。並且是用 perl
寫的,很難看懂。因此仍是決定本身寫一個,只覆蓋 atool
的一部分經常使用功能。)
例子:
# a 用於建立壓縮文件 % a a.tgz dir1 file1 file2 dir1/ file1 file2 # al 用於列出壓縮文件中的文件列表 % al a.tgz drwxr-xr-x goreliu/goreliu 0 2017-09-13 11:23 dir1/ -rw-r--r-- goreliu/goreliu 3 2017-09-13 11:23 file1 -rw-r--r-- goreliu/goreliu 3 2017-09-13 11:23 file2 # x 用於解壓文件 % x a.tgz dir1/ file1 file2 a.tgz -> a # 若是解壓後的文件名或目錄名中當前目錄下已經存在,則解壓到隨機目錄 % x a.tgz dir1/ file1 file2 a.tgz -> /tmp/test/x-c4I
思路:
file
命令結果判斷壓縮文件的格式。ln -s
成多個命令。實現:
#!/bin/zsh get_type_by_name() { case $1 { (*.zip|*.7z|*.jar) echo 7z ;; (*.rar|*.iso) echo 7z_r ;; (*.tar|*.tgz|*.txz|*.tbz2|*.tar.*) echo tar ;; (*.cpio) echo cpio ;; (*.cpio.*) echo cpio_r ;; (*.gz) echo gz ;; (*.xz) echo xz ;; (*.bz2) echo bz2 ;; (*.lzma) echo lzma ;; (*.lz4) echo lz4 ;; (*.ar) echo ar ;; (*) return 1 ;; } } get_type_by_file() { case $(file -bz $1) { (Zip *|7-zip *) echo 7z ;; (RAR *) echo 7z_r ;; (POSIX tar *|tar archive) echo tar ;; (*cpio archive*) echo cpio ;; (*gzip *) echo gz ;; (*XZ *) echo xz ;; (*bzip2 *) echo bz2 ;; (*LZMA *) echo lzma ;; (*LZ4 *) echo lz4 ;; (current ar archive) echo ar ;; (*) return 1 ;; } } (($+commands[tar])) || alias tar=bsdtar (($+commands[cpio])) || alias cpio=bsdcpio case ${0:t} { (a) (($#* >= 2)) || { echo Usage: $0 target files/dirs return 1 } case $(get_type_by_name $1) { (7z) 7z a $1 $*[2,-1] ;; (tar) tar -cavf $1 $*[2,-1] ;; (cpio) find $*[2,-1] -print0 | cpio -H newc -0ov > $1 ;; (gz) gzip -cv $*[2,-1] > $1 ;; (xz) xz -cv $*[2,-1] > $1 ;; (bz2) bzip2 -cv $*[2,-1] > $1 ;; (lzma) lzma -cv $*[2,-1] > $1 ;; (lz4) lz4 -cv $2 > $1 ;; (ar) ar rv $1 $*[2,-1] ;; (*) echo $1: error return 1 ;; } ;; (al) (($#* >= 1)) || { echo Usage: $0 files return 1 } for i ($*) { case $(get_type_by_name $i || get_type_by_file $i) { (7z|7z_r) 7z l $i ;; (tar) tar -tavf $i ;; (cpio|cpio_r) cpio -itv < $i ;; (gz) zcat $i ;; (xz) xzcat $i ;; (bz2) bzcat $i ;; (lzma) lzcat $i ;; (lz4) lz4cat $i ;; (ar) ar tv $i ;; (*) echo $i: error ;; } } ;; (x) (($#* >= 1)) || { echo Usage: $0 files return 1 } for i ($*) { local outdir=${i%.*} [[ $outdir == *.tar ]] && { outdir=$outdir[1, -5] } if [[ -e $outdir ]] { outdir="$(mktemp -d -p $PWD x-XXX)" } else { mkdir $outdir } case $(get_type_by_name $i || get_type_by_file $i) { (7z|7z_r) 7z x $i -o$outdir ;; (tar) tar -xavf $i -C $outdir ;; (cpio|cpio_r) local file_path=$i [[ $i != /* ]] && file_path=$PWD/$i cd $outdir && cpio -iv < $file_path && cd .. ;; (gz) zcat $i > $outdir/$i[1,-4] ;; (xz) xzcat $i > $outdir/$i[1,-4] ;; (bz2) bzcat $i > $outdir/$i[1,-5] ;; (lzma) lzcat $i > $outdir/$i[1,-6] ;; (lz4) lz4cat $i > $outdir/$i[1,-5] ;; (ar) local file_path=$i [[ $i != /* ]] && file_path=$PWD/$i cd $outdir && ar x $file_path && cd .. ;; (*) echo $i: error ;; } local files=$(ls -A $outdir) if [[ -z $files ]] { rmdir $outdir } elif [[ -e $outdir/$files && ! -e $files ]] { mv -v $outdir/$files . && rmdir $outdir echo $i " -> " $files } else { echo $i " -> " $outdir } } ;; (*) echo error return 1 ;; }
功能:
咱們常常會遇到在循環裏批量處理文件的場景(好比將全部 jpg 圖片轉換成 png 圖片),那麼就會遇到一個麻煩:若是在前臺處理文件,那同一時間只能處理一個,效率過低;若是在後臺處理文件,那麼瞬間就會啓動不少個進程,佔用大量資源,系統難以承受。咱們但願的是在同一時間最多同時處理固定數量(好比 10 個)的文件,若是已經達到了這個數量,那麼就先等一會,直到有退出的進程後再繼續。parallel
命令中在必定程度上能知足這個需求,但用起來太麻煩。
例子:
# rr 是一個函數(可放在 .zshrc 中),直接 rr 加命令便可使用 # 命令中支持變量、重定向等等,格式上和直接輸入命令沒有區別(不支持 alias) % rr sleep 5 [4] 5031 % rr sleep 5 [5] 5032 # 若是不加參數,則顯示當前運行的進程數、最大進程併發數和運行中進程的進程號 # 默認最大進程併發數是 10 % rr running/max: 2/10 pid: 5031 5032 # 5 秒以後,運行結束 % rr running/max: 0/10 # 用 -j 來指定最大進程併發數,指定一次便可,如需修改可再次指定 # 能夠只調整最大進程併發數而不運行命令 % rr -j2 sleep 10 [4] 5035 % rr sleep 10 [5] 5036 # 超過了最大進程併發數,等待,而且每一秒檢查一次是否有進程退出 # 若是有進程退出,則繼續在後臺運行當前命令 % rr sleep 10 running/max: 2/2, wait 1s ... pid: 5035 5036 running/max: 2/2, wait 1s ... pid: 5035 5036 [4] - done $* [4] 5039 # 實際使用場景,批量將 jpg 圖片轉換成 png 圖片,gm 是 graphicsmagick 中的命令 # 轉換圖片格式比較耗時,順序執行的話須要好久 % for i (*.jpg) { rr gm convert $i ${i/jpg/png} } [4] 5055 [5] 5056 [6] 5057 [7] 5058 [8] 5059 [9] 5060 [10] 5061 [11] 5062 [12] 5063 [13] 5064 running/max: 10/10, wait 1s ... pid: 5060 5061 5062 5063 5064 5055 5056 5057 5058 5059 running/max: 10/10, wait 1s ... pid: 5060 5061 5062 5063 5064 5055 5056 5057 5058 5059 [11] done $* [5] done $* [5] 5067 [12] done $* [11] 5068 [6] done $* [6] 5069 [12] 5070 running/max: 10/10, wait 1s ... pid: 5070 5060 5061 5064 5055 5067 5068 5069 5058 5059 [13] - done $* [4] done $* [4] 5072 [13] 5073 running/max: 10/10, wait 1s ... pid: 5070 5060 5072 5061 5073 5067 5068 5069 5058 5059 [5] done $* [6] done $* [5] 5075 [6] 5076 running/max: 10/10, wait 1s ... pid: 5070 5060 5072 5061 5073 5075 5076 5068 5058 5059 ...
思路:
實現:
rr() { (($+max_process)) || typeset -g max_process=10 (($+running_process)) || typeset -gA running_process=() [[ $1 == -j<1-> ]] && { max_process=${1[3,-1]} shift } (($# == 0)) && { for i (${(k)running_process}) { [[ -e /proc/$i ]] || unset "running_process[$i]" } echo "running/max: $#running_process/$max_process" (($#running_process > 0)) && echo "pid: ${(k)running_process}" return } while ((1)) { local running_process_num=$#running_process if (($running_process_num < max_process)) { $* & running_process[$!]=1 return } for i (${(k)running_process}) { [[ -e /proc/$i ]] || unset "running_process[$i]" } (($#running_process == $running_process_num)) && { echo "running/max: $running_process_num/$max_process, wait 1s ..." echo "pid: ${(k)running_process}" sleep 1 } } }
功能:
將當前目錄及子目錄的全部常見圖片格式轉換成 jpg 格式(jpg 格式也要轉換一遍,能夠減小文件體積),而後刪除原圖片。須要用 5 個併發進程來處理。注意避免僅擴展名不一樣的文件互相覆蓋的狀況。
例子:
% tree . ├── mine │ ├── 信.txt │ ├── 第一封信.jpg │ └── 第二封信.JPG ├── 搞笑 │ ├── 賣萌.GIF │ ├── 貓吃魚.gif │ └── 貓搶東西吃.gif └── 素材 ├── 104 按鍵模板.jpg ├── 104 按鍵模板.psd ├── ahk │ ├── ahk_bg.jpg │ ├── ahk_home_logo.jpg │ ├── ahk_home_logo.txt │ ├── ahk_home_qr.jpg │ ├── ahk_home_qr_small.jpg │ └── ahk_logo.png ├── stp_fc_cw_png_pk │ ├── HD.PNG │ ├── newimage.png │ ├── nshd.PNG │ └── std.png ├── 地球.jpg ├── 星系.JPEG ├── 木紋 背景.GIF ├── 木紋 背景.jpeg └── 木紋 背景.jpg 5 directories, 23 files % alltojpg running/max: 0/5 running: 5, wait 1.0000000000s ... pid: 5953 5954 5955 5956 5957 running: 5, wait 1.0000000000s ... pid: 5965 5966 5967 5968 5959 % tree . ├── mine │ ├── 信.txt │ ├── 第一封信.jpg │ └── 第二封信.jpg ├── 搞笑 │ ├── 賣萌_g.jpg │ ├── 貓吃魚_g.jpg │ └── 貓搶東西吃_g.jpg └── 素材 ├── 104 按鍵模板.jpg ├── 104 按鍵模板.psd ├── ahk │ ├── ahk_bg.jpg │ ├── ahk_home_logo.jpg │ ├── ahk_home_logo.txt │ ├── ahk_home_qr.jpg │ ├── ahk_home_qr_small.jpg │ └── ahk_logo_p.jpg ├── stp_fc_cw_png_pk │ ├── HD_p.jpg │ ├── newimage_p.jpg │ ├── nshd_p.jpg │ └── std_p.jpg ├── 地球.jpg ├── 星系_e.jpg ├── 木紋 背景_e.jpg ├── 木紋 背景_g.jpg └── 木紋 背景.jpg 5 directories, 23 files
思路:
gm convert
命令(graphicsmagick 中)或者 convert
命令(imagemagick 中)。jpg
jpeg
png
gif
,另外多是大寫的擴展名。a.gif
覆蓋 a.jpg
的狀況,爲不一樣的文件格式添加不一樣後綴,這樣能夠無需檢查是否有同名文件,加快速度。實現:
#!/bin/zsh # rr 是上一個實例中代碼的改進版本 rr() { (($+max_process)) || typeset -gi max_process=10 (($+check_interval)) || typeset -gF check_interval=1 (($+running_process)) || typeset -gA running_process=() while {getopts i:j:h arg} { case $arg { (i) ((OPTARG > 0)) && check_interval=$OPTARG ;; (j) ((OPTARG > 0)) && max_process=$OPTARG ;; (h) echo "Usage: $0 [-i check_interval] [-j max_process] [cmd] [args]" return ;; } } shift $((OPTIND - 1)) (($# == 0)) && { for i (${(k)running_process}) { [[ -e /proc/$i ]] || unset "running_process[$i]" } echo "running/max: $#running_process/$max_process" (($#running_process > 0)) && echo "pid: ${(k)running_process}" return 0 } while ((1)) { local running_process_num=$#running_process if (($running_process_num < max_process)) { $* & running_process[$!]=1 return } for i (${(k)running_process}) { [[ -e /proc/$i ]] || unset "running_process[$i]" } (($#running_process == $running_process_num)) && { echo "running: $running_process_num, wait ${check_interval}s ..." echo "pid: ${(k)running_process}" sleep $check_interval } } } # JPG 做爲中間擴展名 rename .JPG .jpg **/*.JPG # 設置進程併發數爲 5 rr -j5 for i (**/*.(jpg|png|PNG|jpeg|JPEG|gif|GIF)) { rr gm convert $i $i.JPG } # 等全部操做結束 wait # 刪除原文件 rm **/*.(jpg|png|PNG|jpeg|JPEG|gif|GIF) # 避免覆蓋同名文件 rename .jpg.JPG .jpg **/*.JPG rename .png.JPG _p.jpg **/*.JPG rename .PNG.JPG _p.jpg **/*.JPG rename .jpeg.JPG _e.jpg **/*.JPG rename .JPEG.JPG _e.jpg **/*.JPG rename .gif.JPG _g.jpg **/*.JPG rename .GIF.JPG _g.jpg **/*.JPG
本文講解了幾個比較實用的 zsh 腳本,後續可能會補充更多個。
2017.09.13:新增「實例七」、「實例八」和「實例九」。
本文再也不更新,全系列文章在此更新維護:github.com/goreliu/zshguide
付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活訂價,歡迎諮詢,微信 ly50247。