至此,咱們介紹了linux系統中經常使用命令的使用方法,簡述了bash程序的使用方法和工做流程。在使用bash編寫腳本程序時,熟練掌握這些工具的用法,每每可以達到事半功倍的效果。html
本文將經過講述一些實例,試着探討bash腳本編程的技巧。須要說明的是,這裏的技巧是多角度尋求解決方案的思路,是創建在對各類命令和bash編程技法深入理解的基礎之上的。linux
先來看某公司的兩個筆試題:shell
一、寫腳本實現,能夠用shell、perl等。在目錄/tmp下找到100個以abc開頭的文件,而後把這些文件的第一行保存到文件new中。編程
分析:尋找名字符合某個模式的文件能夠用find
,但find
不能控制尋找到的文件數量,也許能夠用for
循環控制一下,查看文件的第一行有許多方法,能夠用head
、sed
等。segmentfault
根據以上思路寫出腳本:數組
#!/bin/bash for name in `find /tmp -type f ! -empty -name 'abc*'` do head -1 $name >> new && ((i++)) [[ $i -eq 100 ]] && break done
腳本中每次成功寫入文件new
中一行內容就令變量i
自增,當i增加到100時,當即結束循環。bash
另外一種方案:服務器
#!/bin/bash sed 100q <(head -qn1 $(find /tmp -type f -name 'abc*')) >new
此方案只有一條命令,也不難理解:$(find ...)
部分得到文件名列表,<(head ...)
部分獲取每一個文件的第一行(<(...)
的用法請看這裏),最後sed 100q ... >new
取前100行寫入文件new。併發
二、寫腳本實現,能夠用shell、perl等。把文件b中有的,可是文件a中沒有的全部行,保存爲文件c,並統計c的行數。函數
問題沒什麼可分析的,直接的解決方案:
#!/bin/bash cat b|while read line do if ! grep -xq $line a;then echo $line >>c fi done wc -l c
腳本經過循環讀取文件b中的每一行,判斷該行,若是該行不屬於文件a,則輸出該行內容到文件c中,循環結束後用wc
統計文件c的行數。
另外一種方案:
#!/bin/bash grep -vxf a b|tee c|wc -l
此方案利用grep
的-f
選項將文件a中的每行最爲匹配模式匹配文件b的內容,-v
表示不匹配,而後經過管道交給命令tee
寫入文件c中,而後在經過管道將標準輸出交給wc
命令統計行數。
在使用linux服務器的過程中,隨着服務的長時間運行,有時會有刪除服務日誌的需求。因爲日誌文件正在被該服務所使用,並不能直接進行刪除(準確說是:即便直接刪除了,空間也沒有獲得釋放,須要將服務重啓),比較好的作法是利用重定向清空該文件(如:>some.log
),既釋放了空間,也不用重啓服務。
但當須要清空的文件較多時,手動一個一個清空文件也有許多不方便,不如將需求寫成腳本。
方案1:
#!/bin/bash for log in `find /logs -name 'access_*.log'` do >$i done
腳本很容易理解,也能夠用命令find /logs -name 'access_*.log' -exec cp /dev/null {} \;
。
但還是一個文件執行一次,能不能一次性執行完呢?
方案2:
#!/bin/bash find /logs -name 'access_*.log'|xargs tee
此方案巧妙的利用了命令xargs
和tee
將find找到的文件一次性清空。
假設要對一個較大文件分別給不一樣的程序處理,並收集處理結果。
一般的處理的辦法多是串行的處理該文件,但若是各個程序須要較長的處理時間,串行處理將不能有效的利用機器的性能,若是不一樣的處理程序在後臺併發運行,相似這樣:cat file|command1 &
,cat file|command2 &
,cat file|command3 &
...
這樣處理能充分發揮服務器性能,但它的一個問題是,若是文件較大,對內存的消耗也會很大。
一種解決方案是:
#!/bin/bash cat file|tee >(command1) >(command2) ... > >(commandN)|cat
此種方案的一個問題是,多個處理結果是隨機的,若是須要處理結果是有序的(好比按命令的順序輸出),則不能知足需求。
另外,tee
命令分發的速率是恆定的,因此只能按處理命令中最慢的速率分發,它們的輸出將爭用同一個管道,必定條件下,有可能形成死鎖。
另外一種解決方案:
#!/bin/bash #定義處理函數 f() { mkfifo p{i,o}{1,2,3} command1 < pi1 > po1 & command2 < pi2 > po2 & command3 < pi3 > po3 & tee pi{1,2} > pi3 & cat po{1,2,3} rm -f p{i,o}{1,2,3} } cat file | f
此方案利用命名管道(見這裏)處理分發及彙總各命令的輸出,而後經過cat依次讀取處理後的結果。
咱們在描述重定向與管道的文章中講述過一種併發方式,下面介紹另外一種。
咱們說過,命令替換
的問題是命令的當即執行而後等待結果,此時shell沒法傳入輸入。bash使用一個稱爲進程替換
的功能來彌補這些不足,進程替換
其實是命令替換
和管道
的組合,和命令替換
相似,bash運行一個命令,但令其運行於後臺而再也不等待其完成。關鍵在於Bash爲這條命令打開了一個用於讀和寫的管道,而且綁定到一個文件名,最後展開爲結果。
利用進程替換
的這一特性,能夠想到另一種併發的方式:
#!/bin/bash #處理函數,假設該函數的處理結果有且只有一個值 sth_todo() { #須要對第一個參數處理的命令 some_command $1 } #文件數組,也能夠是其餘待處理數據 file_list=('<(sth_todo '{1..10}.log')') #展開形如:<(sth_todo 1.log) <(sth_todo 2.log) <(sth_todo 3.log) ... #收集結果並賦值給數組 read -a result <<<$(eval cat "${file_list[@]}") #輸出 echo "${result[@]}"
腳本中須要注意的地方在於數組的賦值和eval
的運用(見這裏)。
固然處理函數並不必定只有惟一結果,若有其餘結果,只需將收集結果部分作相應更改便可。
假定有須要取兩個數組的交集(或並集、差集),簡單的作法無非是兩個循環對比兩個數組中的每一個值,取得相同的部分:
#!/bin/bash list_1=(...) list_2=(...) for i in ${list_1[@]} do for j in ${list_2[@]} do [[ "$i" == "$j" ]] && echo $i done done
再看另外一種方案:
list_1=(...) list_2=(...) #重置IFS值 IFS=$'\n' #交集 grep -xf <(echo "${list_1[*]}") <<<"${list_2[*]}" #並集 sort -u <<EOF ${ip[*]} ${ip_[*]} EOF #差集之一 grep -vxf <(echo "${list_1[*]}") <<<"${list_2[*]}" #還原IFS IFS=$' \t\n'
bash的一些特性和經常使用命令結合使用,使本來須要許多循環代碼解決的問題變得「垂手可得」。但本例中,須要重點理解的是:IFS
在數組擴展中的特性,命令grep
和sort
的運用,以及進程替換
的使用。
假如須要對大量小文件進行簡單的文本替換,而文件量已達到不可一次性處理的程度(好比幾百萬個)。
此時若是採用通常的處理辦法,例如
find . -name '*.html' -exec sed -i 's/xxxx/oooo/g' {} \;
或相似的命令,顯然,這樣一個文件接着一個文件串行處理將花費巨大的時間成本。
對於此類問題,須要在服務器性能和時間成本上作取捨,先給出處理方案:
#!/bin/bash #取得待處理文件數組 A=($(find . -name '*.html')) #每10000個文件後臺循環處理 for((i=0;i<${#A[@]};i+=10000)) do sed -i 's@xxxx@oooo@g' ${A[@]:$i:10000} & done #等待全部進程退出 wait
此方案假定服務器資源能夠隨意使用,只爲達到時間效率最大化。若是須要控制服務器資源消耗(主要是IO性能),能夠結合這一篇,控制併發的進程數量。
關於bash的文章,至此就告一段落了。
個人博客即將同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/dev...