溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何優(yōu)化Shell腳本效率

發(fā)布時(shí)間:2021-10-13 14:30:37 來(lái)源:億速云 閱讀:145 作者:小新 欄目:開(kāi)發(fā)技術(shù)

這篇文章將為大家詳細(xì)講解有關(guān)如何優(yōu)化Shell腳本效率,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

一、先說(shuō)一下Shell腳本語(yǔ)言自身的局限性

作為解釋型的腳本語(yǔ)言,天生就有效率上邊的缺陷。盡管它調(diào)用的其他命令可能效率上是不錯(cuò)的。
Shell腳本程序的執(zhí)行是順序執(zhí)行,而非并行執(zhí)行的。這很大程度上浪費(fèi)了可能能利用上的系統(tǒng)資源。
Shell每執(zhí)行一個(gè)命令就創(chuàng)建一個(gè)新的進(jìn)程,如果腳本編寫(xiě)者沒(méi)有這方面意識(shí),編寫(xiě)腳本不當(dāng)?shù)脑?,是非常浪費(fèi)系統(tǒng)資源的。

二、我們?cè)赟hell腳本語(yǔ)言的局限性上盡可能的通過(guò)我們有經(jīng)驗(yàn)的編碼來(lái)提高腳本的效率。

1、比如我想做一個(gè)循環(huán)處理數(shù)據(jù),可能是簡(jiǎn)單的處理一下數(shù)據(jù),這樣會(huì)讓人比較容易就想到Shell里的循環(huán)類似這樣:

代碼如下:


sum=0
for((i=0;i<100000;i++))
do
sum=$(($sum+$i))
done
echo $sum


我們可以使用time這個(gè)腳本來(lái)測(cè)試一下十萬(wàn)次循環(huán)的三次執(zhí)行耗時(shí):
real 0m2.115s
user 0m1.975s
sys 0m0.138s

real 0m2.493s
user 0m2.173s
sys 0m0.254s

real 0m2.085s
user 0m1.886s
sys 0m0.195s
平均耗時(shí)2.2s,如果你知道awk命令里的循環(huán)的話,那更好了,我們來(lái)測(cè)試一下同數(shù)據(jù)規(guī)模的循環(huán)三次執(zhí)行耗時(shí):

代碼如下:


awk 'BEGIN{
sum=0;
for(i=0;i<100000;i++)
sum=sum+i;
print sum;
}'


real 0m0.023s
user 0m0.018s
sys 0m0.005s

real 0m0.020s
user 0m0.018s
sys 0m0.002s

real 0m0.021s
user 0m0.019s
sys 0m0.003s
你都不敢想象平均時(shí)間僅0.022s,基本上純循環(huán)的效率已經(jīng)比Shell高出兩位數(shù)量級(jí)了。事實(shí)上你再跑百萬(wàn)次的循環(huán)你會(huì)發(fā)現(xiàn)Shell已經(jīng)比較吃力了,千萬(wàn)級(jí)的更是艱難。所以你應(yīng)該注意你的程序盡量使用awk來(lái)做循環(huán)操作。

2、關(guān)于正則,經(jīng)常寫(xiě)Shell的同學(xué)都明白它的重要性,但是你真的能高效使用它嗎?
下邊舉個(gè)例子:現(xiàn)在我有一個(gè)1694617行的日志文件 action.log,它的內(nèi)容類似:
2012_02_07 00:00:04 1977575701 183.10.69.47 login 500004 1977575701 old /***/port/***.php?…
我現(xiàn)在想獲取//之間的port的字符串,我可以這樣:
awk -F'/' ‘{print $3}' < 7action.log > /dev/null
但是你不會(huì)想知道它的效率:
real 0m12.296s
user 0m12.033s
sys 0m0.262s
相信我,我不會(huì)再想看著光標(biāo)閃12秒的。但是如果這樣執(zhí)行:
awk ‘{print $9}' < 7action.log | awk -F'/' '{print $3}' > /dev/null
這句的效率三次分別是:
real 0m3.691s
user 0m5.219s
sys 0m0.630s

real 0m3.660s
user 0m5.169s
sys 0m0.618s

real 0m3.660s
user 0m5.150s
sys 0m0.612s
平均時(shí)間大概3.6秒,這前后效率大概有4倍的差距,雖然不像上一個(gè)有百倍的差距,但是也足夠讓4小時(shí)變成1小時(shí)了。我想你懂這個(gè)差距的。

其實(shí)這個(gè)正則實(shí)例你可以嘗試推測(cè)其他的情況,因?yàn)檎齽t每次運(yùn)行都是需要啟動(dòng)字符串匹配的,而且默認(rèn)的分隔符會(huì)較快的按字段區(qū)分出。所以我們?cè)谥酪恍?shù)據(jù)規(guī)律之后可以嘗試大幅度的縮短我們將要進(jìn)行復(fù)雜正則匹配的字符串,這樣會(huì)根據(jù)你縮減數(shù)據(jù)規(guī)模有一個(gè)非常明顯的效率提升,上邊還是驗(yàn)證的比較簡(jiǎn)單的正則匹配情況,只有一個(gè)單字符“\”,你可以試想如果正則表達(dá)式是這樣:
$7!~/\.jpg$/&&$7~/\.[s]?html|\.php|\.xml|\/$/&&($9==200||$9==304)&&$1!~/^103\.108|^224\.215|^127\.0|^122\.110\.5/
我想你可以想象的出一個(gè)目標(biāo)匹配字符串從500個(gè)字符縮減到50個(gè)字符的時(shí)候的巨大意義!

ps:另外詳細(xì)的正則優(yōu)化請(qǐng)看這個(gè)日期之后發(fā)的一篇博文。

3、再說(shuō)一下shell的重定向和管道。這個(gè)條目我不會(huì)再舉例子,只是說(shuō)一下我個(gè)人的理解。
周所周知,很多程序或者語(yǔ)言都有一個(gè)比較突出的效率瓶頸就是IO,Shell也不例外(個(gè)人這么考慮)。所以建議盡可能的少用重定向來(lái)進(jìn)行輸入輸出這樣的操作或者創(chuàng)建臨時(shí)文件來(lái)供后續(xù)使用,當(dāng)然,如果必須這么干的時(shí)候那就這么干吧,我只是講一個(gè)盡量的過(guò)程。
我們可以用Shell提供的管道來(lái)實(shí)現(xiàn)命令間數(shù)據(jù)的傳遞。如果進(jìn)行連續(xù)的對(duì)數(shù)據(jù)進(jìn)行過(guò)濾性命令的時(shí)候,盡量把一次性過(guò)濾較多的命令放在前邊,這個(gè)原因都懂吧?減少數(shù)據(jù)傳遞規(guī)模。
最后我想說(shuō)的連管道也盡量的少用的,雖然管道比正常的同定向IO快幾個(gè)數(shù)量級(jí)的樣子,但是那也是需要消耗額外的資源的,好好設(shè)計(jì)你的代碼來(lái)減少這個(gè)開(kāi)銷吧。比如sort | uniq 命令,完全可以使用 sort -u 來(lái)實(shí)現(xiàn)。

4、再說(shuō)一下Shell腳本程序的順序執(zhí)行。這塊的優(yōu)化取決于你的系統(tǒng)負(fù)載是否達(dá)到了極限,如果你的系統(tǒng)連命令的順序執(zhí)行負(fù)載都到了一個(gè)較高的線的話,你就沒(méi)有必要進(jìn)行Shell腳本程序的并行改造了。下邊給出一個(gè)例子,如果你要模仿這個(gè)優(yōu)化,請(qǐng)保證你的系統(tǒng)還能有負(fù)載空間。比如現(xiàn)在有這樣一個(gè)程序:
supportdatacommand1
supportdatacommand2
supportdatacommand3
supportdatacommand4
supportdatacommand5
supportdatacommand6

need13datacommand
need24datacommand
need56datacommand
大意就是有6個(gè)提供數(shù)據(jù)的命令在前邊,后面有3個(gè)需要數(shù)據(jù)的命令,第一個(gè)需要數(shù)據(jù)的命令需要數(shù)據(jù)13,第二個(gè)需要24,第三個(gè)需要56。但是正常情況下Shell會(huì)順序的執(zhí)行這些命令,從supportdatacommand1,一條一條執(zhí)行到need56datacommand。這樣的過(guò)程你看著是不是也很蛋疼?明明可以更好的做這一塊的,蛋疼的程序可以這樣改造:

代碼如下:


supportdatacommand1 &
supportdatacommand2 &
supportdatacommand3 &
supportdatacommand4 &
supportdatacommand5 &
supportdatacommand6 &
#2012-02-22 ps:這里的循環(huán)判斷后臺(tái)命令是否執(zhí)行完畢是有問(wèn)題的,pidnum循#環(huán)減到最后也還是1不會(huì)得到0值,具體解決辦法看附錄,因?yàn)檫€有解釋,就不在這#里添加和修改了。
while true
do
sleep 10s
pidnum=`jobs -p | wc -l`
if [ $pidnum -le 0 ]
then
echo "run over"
break
fi
done

need13datacommand &
need24datacommand &
need56datacommand &

wait
...


可以類似上邊的改造。這樣改造之后蛋疼之感就紓解的多了。但還是感覺(jué)不是很暢快,那好吧,我們可以再暢快一點(diǎn)(我是指程序。。。),可以類似這樣:

代碼如下:


for((i=0;i<1;i++));do
{
command1
command2
}&
done

for((i=0;i<1;i++));do
{
command3&
command4&
}&
done

for((i=0;i<1;i++));do
{
command5 &
command6 &
if 5 6執(zhí)行完畢...
command7
}&
done


這樣類似這樣的改造,讓有前后關(guān)系的命令放在一個(gè)for循環(huán)里讓他們一起執(zhí)行去,這樣三個(gè)for循環(huán)其實(shí)是并行執(zhí)行了。然后for循環(huán)內(nèi)部的命令你還可以類似改造1的那種方式改造或者內(nèi)嵌改造2這個(gè)的并行for循環(huán),都是可以的,關(guān)鍵看你想象力了。恩?哦,不對(duì),關(guān)鍵是看這些個(gè)命令之間是一種什么樣的基友關(guān)系了。有關(guān)聯(lián)的放一個(gè)屋里就行了,剩下的你就不用操心了。嘿嘿~~

其實(shí)這個(gè)優(yōu)化真的需要看系統(tǒng)負(fù)載。

5、關(guān)于對(duì)shell命令的理解。這個(gè)條目就靠經(jīng)驗(yàn)了,因?yàn)槊菜茮](méi)有相關(guān)的書(shū)籍可看,如果誰(shuí)知道有,請(qǐng)推薦給我,我會(huì)灰常感謝的啊。
比如:sed -n '45,50p' 和 sed -n '51q;45,50p' ,前者也是讀取45到50行,后者也是,但是后者到51行就執(zhí)行了退出sed命令,避免了后續(xù)的操作讀取。如果這個(gè)目標(biāo)文件的規(guī)模巨大的話,剩下的你懂的。
還有類似sed ‘s/foo/bar/g' 和sed ‘/foo/ s/foo/bar/g'
sed支持采用正則進(jìn)行匹配和替換,考慮字符串替換的需求中,不防加上地址以提高速度。實(shí)例中通過(guò)增加一個(gè)判斷邏輯,采用“事先匹配”代替“直接替換”,由于sed會(huì)保留前一次的正則匹配環(huán)境,不會(huì)產(chǎn)生冗余的正則匹配,因此后者具有更高的效率。關(guān)于sed命令的這兩點(diǎn)優(yōu)化,我也在sed命令詳解里有提到。

還有類似sort 如果數(shù)字盡量用 -n選項(xiàng);還有統(tǒng)計(jì)文件行數(shù),如果每行的數(shù)據(jù)在占用字節(jié)數(shù)一樣的情況時(shí)就可以ls查文件大小然后除以每行的數(shù)據(jù)大小的出行數(shù),而避免直接使用wc -l這樣的命令;還有find出來(lái)的數(shù)據(jù),別直接就-exec選項(xiàng)了,如果數(shù)據(jù)規(guī)模小很好,但是如果你find出來(lái)上千條數(shù)據(jù)或更多,你會(huì)瘋掉的,不,系統(tǒng)會(huì)瘋掉的,因?yàn)槊啃袛?shù)據(jù)都會(huì)產(chǎn)生新的進(jìn)程,你可以這樣find …. | xargs ….;還有…(如果你也知道類似的提效率情況請(qǐng)你告訴我共同進(jìn)步!)

三、關(guān)于優(yōu)化更好的一些選擇

一個(gè)比較好的提升Shell腳本的效率方法就是…… 就是…… 就是…… 好吧,就是盡量少用Shell(別打我?。。。。┫逻吔o出一些debian官方統(tǒng)計(jì)的一些在linux系統(tǒng)上邊的各個(gè)語(yǔ)言的效率圖,咱都以C++為比較基準(zhǔn)(系統(tǒng)規(guī)格:x64 Ubuntu? Intel&reg; Q6600&reg; quad-core):
這些圖的查看方法,比如第一個(gè)圖java和c++的程序效率比較圖,總共分三個(gè)部分,分別是time、memory、code的比較,如果是c++/java ,就是說(shuō) c++做比較的分子,java做比較的分母,如果圖上的長(zhǎng)條在哪邊,說(shuō)明所在的那邊的程序使用的時(shí)間或者內(nèi)存或者代碼較多,具體多多少就看長(zhǎng)條長(zhǎng)了多少。每一部分有多個(gè)長(zhǎng)條圖形,每個(gè)長(zhǎng)條圖案表示針對(duì)程序處理不同方面的任務(wù)時(shí)進(jìn)行的測(cè)試。比如第一幅,c++和java在該環(huán)境下大部分情況下time上是差不多的,甚至java-server還有稍微的優(yōu)勢(shì),內(nèi)存方面c++就有很大優(yōu)勢(shì),能夠使用比java少的多的內(nèi)容做相同的事情,但是編碼量c++就稍微多一點(diǎn)點(diǎn)。以下的圖類似。
如何優(yōu)化Shell腳本效率

如何優(yōu)化Shell腳本效率

如何優(yōu)化Shell腳本效率

如何優(yōu)化Shell腳本效率

通過(guò)上邊的圖我看可以知道C++在時(shí)間和空間上對(duì)Python、Perl、PHP有著絕對(duì)壓倒性的優(yōu)勢(shì),但是相對(duì)的編碼量較高。同java比只有內(nèi)存使用上的優(yōu)勢(shì)。但是我們這篇主要是針對(duì)Shell的,但是,又是但是,debian官網(wǎng)沒(méi)有把shell腳本納入效率比較的統(tǒng)計(jì)范圍?。。?!還是但是,我們知道Python、Perl、PHP都是號(hào)稱對(duì)Shell在效率方面有著明顯的優(yōu)勢(shì),所以你如果不滿意你通過(guò)以上提供的種種優(yōu)化途徑后的Shell腳本程序的話,那你就可以嘗試換一種語(yǔ)言了。

但是我們往往不那么容易舍棄這么好用方便而且簡(jiǎn)單的處理數(shù)據(jù)方式,也可以有個(gè)折中的方法,你先用time測(cè)試各個(gè)Shell腳本命令的耗時(shí),針對(duì)特別耗時(shí),特別讓人不能忍受的命令的效率使用C++程序處理,讓你的Shell腳本來(lái)調(diào)用這個(gè)針對(duì)局部數(shù)據(jù)處理的C++程序,這樣折中貌似還是能讓人接受吧?

四、最后說(shuō)一下這篇是不敢稱為全面或者詳解的文章,是我對(duì)這一段Shell學(xué)習(xí)和實(shí)踐的一些心得,希望能有高手指點(diǎn)。也希望能幫到新踏入這一領(lǐng)域的新同學(xué)。以后有新的心得再添加吧。

感謝這篇文章的作者的博文指點(diǎn)。

2012-02-22 ps:循環(huán)檢測(cè)后臺(tái)命令是否結(jié)束的判斷修改:
解決方法暫時(shí)有兩個(gè)(具體沒(méi)有解釋,不太清楚原因):
1、

代碼如下:


sleep 8 &
sleep 16 &
while true
do
echo `jobs -p | wc -l`
jobs -l >> res
sleep 4
done


2、 檢查剩余個(gè)數(shù)的語(yǔ)句改成 jobs -l |grep -v “Done”|wc -l

第一個(gè)方案的解決是多執(zhí)行一次jobs,可以解釋成為了消除最后的Done結(jié)果,但是這種解釋也是行不通的,因?yàn)檠h(huán)是一直執(zhí)行的,在echo里已經(jīng)執(zhí)行很多次jobs了,何止一次。

第二個(gè)方案是過(guò)濾掉jobs最后的輸出結(jié)果Done這條語(yǔ)句。算是繞過(guò)問(wèn)題得到了期待的結(jié)果。

個(gè)人感覺(jué)bash解釋器優(yōu)化掉了沒(méi)有后臺(tái)命令執(zhí)行的jobs查詢命令,如果是優(yōu)化掉了那也應(yīng)該有個(gè)空的返回,wc依然可以得到0的結(jié)果啊。所以這個(gè)問(wèn)題找不到具體原因,如果你知道請(qǐng)告訴我,非常感謝。。。 這里先感謝just do shell群里的Eric 沉默的土匪 GS 三人,非常感謝你們的幫助。

這里兩個(gè)方法不算好方法,只是奇怪這樣為什么不行,行的又該如何解釋。后來(lái)知道用wait命令就全解決了,耽誤那么多時(shí)間還是用的不明智的方法。

關(guān)于“如何優(yōu)化Shell腳本效率”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI