1. 程式人生 > >提高shell指令碼執行效率

提高shell指令碼執行效率

一、先說一下Shell指令碼語言自身的侷限性

作為解釋型的指令碼語言,天生就有效率上邊的缺陷。儘管它呼叫的其他命令可能效率上是不錯的。
Shell指令碼程式的執行是順序執行,而非並行執行的。這很大程度上浪費了可能能利用上的系統資源。
Shell每執行一個命令就建立一個新的程序,如果指令碼編寫者沒有這方面意識,編寫指令碼不當的話,是非常浪費系統資源的。

二、我們在Shell指令碼語言的侷限性上儘可能的通過我們有經驗的編碼來提高指令碼的效率。

1、比如我想做一個迴圈處理資料,可能是簡單的處理一下資料,這樣會讓人比較容易就想到Shell裡的迴圈類似這樣:
複製程式碼 程式碼如下:
sum=0
for((i=0;i<100000;i++))
do
sum=$(($sum+$i))
done
echo $sum

我們可以使用time這個指令碼來測試一下十萬次迴圈的三次執行耗時:
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
平均耗時2.2s,如果你知道awk命令裡的迴圈的話,那更好了,我們來測試一下同資料規模的迴圈三次執行耗時:
複製程式碼
程式碼如下:
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
你都不敢想象平均時間僅0.022s,基本上純迴圈的效率已經比Shell高出兩位數量級了。事實上你再跑百萬次的迴圈你會發現Shell已經比較吃力了,千萬級的更是艱難。所以你應該注意你的程式儘量使用awk來做迴圈操作。

2、關於正則,經常寫Shell的同學都明白它的重要性,但是你真的能高效使用它嗎?
下邊舉個例子:現在我有一個1694617行的日誌檔案 action.log,它的內容類似:
2012_02_07 00:00:04 1977575701 183.10.69.47 login 500004 1977575701 old /***/port/***.php?…
我現在想獲取//之間的port的字串,我可以這樣:
awk -F'/' ‘{print $3}' < 7action.log > /dev/null
但是你不會想知道它的效率:
real 0m12.296s
user 0m12.033s
sys 0m0.262s
相信我,我不會再想看著游標閃12秒的。但是如果這樣執行:
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
平均時間大概3.6秒,這前後效率大概有4倍的差距,雖然不像上一個有百倍的差距,但是也足夠讓4小時變成1小時了。我想你懂這個差距的。

其實這個正則例項你可以嘗試推測其他的情況,因為正則每次執行都是需要啟動字串匹配的,而且預設的分隔符會較快的按欄位區分出。所以我們在知道一些資料規律之後可以嘗試大幅度的縮短我們將要進行復雜正則匹配的字串,這樣會根據你縮減資料規模有一個非常明顯的效率提升,上邊還是驗證的比較簡單的正則匹配情況,只有一個單字元“\”,你可以試想如果正則表示式是這樣:
$7!~/\.jpg$/&&$7~/\.[s]?html|\.php|\.xml|\/$/&&($9==200||$9==304)&&$1!~/^103\.108|^224\.215|^127\.0|^122\.110\.5/
我想你可以想象的出一個目標匹配字串從500個字元縮減到50個字元的時候的巨大意義!

ps:另外詳細的正則優化請看這個日期之後發的一篇博文。

3、再說一下shell的重定向和管道。這個條目我不會再舉例子,只是說一下我個人的理解。
周所周知,很多程式或者語言都有一個比較突出的效率瓶頸就是IO,Shell也不例外(個人這麼考慮)。所以建議儘可能的少用重定向來進行輸入輸出這樣的操作或者建立臨時檔案來供後續使用,當然,如果必須這麼幹的時候那就這麼幹吧,我只是講一個儘量的過程。
我們可以用Shell提供的管道來實現命令間資料的傳遞。如果進行連續的對資料進行過濾性命令的時候,儘量把一次性過濾較多的命令放在前邊,這個原因都懂吧?減少資料傳遞規模。
最後我想說的連管道也儘量的少用的,雖然管道比正常的同定向IO快幾個數量級的樣子,但是那也是需要消耗額外的資源的,好好設計你的程式碼來減少這個開銷吧。比如sort | uniq 命令,完全可以使用 sort -u 來實現。

4、再說一下Shell指令碼程式的順序執行。這塊的優化取決於你的系統負載是否達到了極限,如果你的系統連命令的順序執行負載都到了一個較高的線的話,你就沒有必要進行Shell指令碼程式的並行改造了。下邊給出一個例子,如果你要模仿這個優化,請保證你的系統還能有負載空間。比如現在有這樣一個程式:
supportdatacommand1
supportdatacommand2
supportdatacommand3
supportdatacommand4
supportdatacommand5
supportdatacommand6

need13datacommand
need24datacommand
need56datacommand
大意就是有6個提供資料的命令在前邊,後面有3個需要資料的命令,第一個需要資料的命令需要資料13,第二個需要24,第三個需要56。但是正常情況下Shell會順序的執行這些命令,從supportdatacommand1,一條一條執行到need56datacommand。這樣的過程你看著是不是也很蛋疼?明明可以更好的做這一塊的,蛋疼的程式可以這樣改造:
複製程式碼
程式碼如下:
supportdatacommand1 &
supportdatacommand2 &
supportdatacommand3 &
supportdatacommand4 &
supportdatacommand5 &
supportdatacommand6 &
#2012-02-22 ps:這裡的迴圈判斷後臺命令是否執行完畢是有問題的,pidnum循#環減到最後也還是1不會得到0值,具體解決辦法看附錄,因為還有解釋,就不在這#裡新增和修改了。
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
...

可以類似上邊的改造。這樣改造之後蛋疼之感就紓解的多了。但還是感覺不是很暢快,那好吧,我們可以再暢快一點(我是指程式。。。),可以類似這樣:
複製程式碼
程式碼如下:
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執行完畢...
command7
}&
done

這樣類似這樣的改造,讓有前後關係的命令放在一個for迴圈裡讓他們一起執行去,這樣三個for迴圈其實是並行執行了。然後for迴圈內部的命令你還可以類似改造1的那種方式改造或者內嵌改造2這個的並行for迴圈,都是可以的,關鍵看你想象力了。恩?哦,不對,關鍵是看這些個命令之間是一種什麼樣的基友關係了。有關聯的放一個屋裡就行了,剩下的你就不用操心了。嘿嘿~~

其實這個優化真的需要看系統負載。

5、關於對shell命令的理解。這個條目就靠經驗了,因為貌似沒有相關的書籍可看,如果誰知道有,請推薦給我,我會灰常感謝的啊。
比如:sed -n '45,50p' 和 sed -n '51q;45,50p' ,前者也是讀取45到50行,後者也是,但是後者到51行就執行了退出sed命令,避免了後續的操作讀取。如果這個目標檔案的規模巨大的話,剩下的你懂的。
還有類似sed ‘s/foo/bar/g' 和sed ‘/foo/ s/foo/bar/g'
sed支援採用正則進行匹配和替換,考慮字串替換的需求中,不防加上地址以提高速度。例項中通過增加一個判斷邏輯,採用“事先匹配”代替“直接替換”,由於sed會保留前一次的正則匹配環境,不會產生冗餘的正則匹配,因此後者具有更高的效率。關於sed命令的這兩點優化,我也在sed命令詳解裡有提到。

還有類似sort 如果數字儘量用 -n選項;還有統計檔案行數,如果每行的資料在佔用位元組數一樣的情況時就可以ls查檔案大小然後除以每行的資料大小的出行數,而避免直接使用wc -l這樣的命令;還有find出來的資料,別直接就-exec選項了,如果資料規模小很好,但是如果你find出來上千條資料或更多,你會瘋掉的,不,系統會瘋掉的,因為每行資料都會產生新的程序,你可以這樣find …. | xargs ….;還有…(如果你也知道類似的提效率情況請你告訴我共同進步!)

三、關於優化更好的一些選擇

一個比較好的提升Shell指令碼的效率方法就是…… 就是…… 就是…… 好吧,就是儘量少用Shell(別打我啊!!!)下邊給出一些debian官方統計的一些在linux系統上邊的各個語言的效率圖,咱都以C++為比較基準(系統規格:x64 Ubuntu™ Intel® Q6600® quad-core):
這些圖的檢視方法,比如第一個圖java和c++的程式效率比較圖,總共分三個部分,分別是time、memory、code的比較,如果是c++/java ,就是說 c++做比較的分子,java做比較的分母,如果圖上的長條在哪邊,說明所在的那邊的程式使用的時間或者記憶體或者程式碼較多,具體多多少就看長條長了多少。每一部分有多個長條圖形,每個長條圖案表示針對程式處理不同方面的任務時進行的測試。比如第一幅,c++和java在該環境下大部分情況下time上是差不多的,甚至java-server還有稍微的優勢,記憶體方面c++就有很大優勢,能夠使用比java少的多的內容做相同的事情,但是編碼量c++就稍微多一點點。以下的圖類似。
java和c++效率相比示意圖

python和c++效率相比示意圖

php和c++效率相比示意圖

perl和c++效率相比示意圖

通過上邊的圖我看可以知道C++在時間和空間上對Python、Perl、PHP有著絕對壓倒性的優勢,但是相對的編碼量較高。同java比只有記憶體使用上的優勢。但是我們這篇主要是針對Shell的,但是,又是但是,debian官網沒有把shell指令碼納入效率比較的統計範圍啊!!!還是但是,我們知道Python、Perl、PHP都是號稱對Shell在效率方面有著明顯的優勢,所以你如果不滿意你通過以上提供的種種優化途徑後的Shell指令碼程式的話,那你就可以嘗試換一種語言了。

但是我們往往不那麼容易捨棄這麼好用方便而且簡單的處理資料方式,也可以有個折中的方法,你先用time測試各個Shell指令碼命令的耗時,針對特別耗時,特別讓人不能忍受的命令的效率使用C++程式處理,讓你的Shell指令碼來呼叫這個針對區域性資料處理的C++程式,這樣折中貌似還是能讓人接受吧?

四、最後說一下這篇是不敢稱為全面或者詳解的文章,是我對這一段Shell學習和實踐的一些心得,希望能有高手指點。也希望能幫到新踏入這一領域的新同學。以後有新的心得再新增吧。

感謝這篇文章的作者的博文指點。

2012-02-22 ps:迴圈檢測後臺命令是否結束的判斷修改:
解決方法暫時有兩個(具體沒有解釋,不太清楚原因):
1、
複製程式碼 程式碼如下:
sleep 8 &
sleep 16 &
while true
do
echo `jobs -p | wc -l`
jobs -l >> res
sleep 4
done

2、 檢查剩餘個數的語句改成 jobs -l |grep -v “Done”|wc -l

第一個方案的解決是多執行一次jobs,可以解釋成為了消除最後的Done結果,但是這種解釋也是行不通的,因為迴圈是一直執行的,在echo裡已經執行很多次jobs了,何止一次。

第二個方案是過濾掉jobs最後的輸出結果Done這條語句。算是繞過問題得到了期待的結果。

個人感覺bash直譯器優化掉了沒有後臺命令執行的jobs查詢命令,如果是優化掉了那也應該有個空的返回,wc依然可以得到0的結果啊。所以這個問題找不到具體原因,如果你知道請告訴我,非常感謝。。。 這裡先感謝just do shell群裡的Eric 沉默的土匪 GS 三人,非常感謝你們的幫助。

這裡兩個方法不算好方法,只是奇怪這樣為什麼不行,行的又該如何解釋。後來知道用wait命令就全解決了,耽誤那麼多時間還是用的不明智的方法。

相關推薦

提高shell指令碼執行效率

一、先說一下Shell指令碼語言自身的侷限性 作為解釋型的指令碼語言,天生就有效率上邊的缺陷。儘管它呼叫的其他命令可能效率上是不錯的。 Shell指令碼程式的執行是順序執行,而非並行執行的。這很大程度上浪費了可能能利用上的系統資源。 Shell每執行一個命令就建立一個新的程序,如果指令碼編寫者沒有這方面意識,

【轉】通過ionice和nice降低shell指令碼執行的優先順序

對於一些執行時會造成系統滿載的指令碼, 例如資料庫備份, 會影響當時其他服務的響應速度, 可以通過ionice和nice對其IO優先順序和CPU優先順序進行調整例如降低"/usr/local/bin/backup.sh"的IO優先順序, 讓其他程序順暢執行: /usr/bin/ionice -c2 -

[轉載] Linux export變數的生命週期和shell的生命週期相同,即shell指令碼執行完畢後,相應的export變數便失效了

說說shell指令碼中的export 和 source,bash 小弟剛剛接觸linux,對linux上的很多東西都比較陌生,所以寫一寫部落格,當做自己工作的總結和技術的積累吧,也是鞭策自己不斷努力的去學習。 今天之所以起這個標題,把export,source ,bash這三個命令放在一起講

shell指令碼執行已有的其他指令碼檔案

工作中常遇到一些資料問題,需要各種指令碼去處理,各種先後順序,還要處理多個庫,還會遇到某個指令碼處理某個庫時報錯需要重新處理的問題,因此用一個shell把它們串起來就比較方便了 1.多個庫執行同一個指令碼 #/bin/bash city_array=('bj' 'sh' 'tj' )

shell指令碼執行sql檔案及語句

由於經常操作資料庫,遇到頻繁建表、加欄位、索引、修改資料等問題,大多是操作多個數據庫,sql檔案或語句不固定,最後選擇shell指令碼來執行 eg: 1.多庫建相同的表 #/bin/bash host='127.0.0.1' dbUser='root' dbPassword=

利用shell指令碼執行mongdb命令

用shell指令碼執行mongdb的renameCollection命令: 1、建立一個*.sh檔案 2、增加如下命令: #!/bin/bash mycol="test" mongo 127.0.0.1:27017/pica -u pica_user -p "[email 

WebDriverWait智慧等待查詢元素,提高程式碼的執行效率,不浪費時間,減少程式碼量

from selenium.webdriver.support.wait import WebDriverWait智慧等待10s之後獲取元素,獲取的是單個元素def find_element(self, locator):WebDriverWait(self.driver, 10).until(lambda

springcloud 叢集部署jar檔案,shell指令碼執行

本文主要是記錄springcloud的註冊中心的高可用、叢集部署、專案打包jar檔案編寫shell指令碼進行部署。 首選我們準備一個註冊中心jar包,裡面有兩個配置檔案 application-eureka1.properties   : #服務埠 server.por

kylin調優,專案中錯誤總結,知識點總結,kylin jdbc driver + 資料庫連線池druid + Mybatis專案中的整合,shell指令碼執行kylin restapi 案例

關於本篇文章的說明: 本篇文章為筆者辛苦勞作用了一整天總結出來的文件,大家閱讀轉發的時候請不要吝嗇寫上筆者:塗作權 和 原文地址。 由於筆者所在環境沒有人用過kylin,筆者也是自學官網,閱讀書籍 將kylin用於實際專案,期間遇到了很多很多關於kylin使用的問題。為了讓後面的人在

shell指令碼執行返回的狀態碼

不管是在執行什麼樣語言編寫的程式,都需要獲得其被呼叫函式或程式的退出狀態或(及)返回值,以便於我們好根據退出狀態或(及)返回值判斷當前被呼叫的函式或程式的執行結果是否成功等,然後好做進一步的處理,如提示使用者出錯了,還是繼續執行等。這裡先就退出狀態和返回值做一下名詞解釋:退出狀態:執行某一函式或者程式之後通過

Shell指令碼執行Hive語句

#!/bin/bash #give the params: name value tablename if [ $# -eq 3 ] then name=$1 value=$2 tablename=$3 echo name=${name} echo value=${value} echo

Shell指令碼執行hive語句 | hive以日期建立分割槽表 | linux schedule程式 | sed替換檔案字串 | shell判斷hdfs檔案目錄是否存在

#!/bin/bash source /etc/profile; ################################################## # Author: ouyangyewei # #

使用shell指令碼執行hive、sqoop命令

1、test.sh指令碼內容如下: #!/bin/bash #CURR_DATE=`date +"%Y-%m-%d %H:%M:%S"`------>不能使用v_sql="insert into

在Oozie 中排程執行shell、hive 指令碼,以及通過shell指令碼執行hive/sqoop/shell指令碼的方法

  最近專案需要用到OOzie工具,可是找了好久,也沒有找到一個完整的、統一的解決和部署方案。經過努力,終於打通了其中的所有環節,解決了各種坑爹的問題。   首先,就專案需求做一個簡單的介紹:      專案需要從mysql中匯入資料到hive進行離線計算後,再導回到mys

shell 指令碼執行python指令碼,連線hive提交資料寫入表

使用說明 1.cd /opt/zy 在這個目錄下以root使用者許可權執行命令 2. 在SAP查詢的時候 Tcode:ZMMR0005 Purchase Org * PO Creating:2017/3/1 (開始日期) 2017/

shell指令碼執行hive命令傳值給sql檔案、shell傳參

使用場景,大資料平臺azkaban任務中通過shell指令碼呼叫sql檔案(尤其是呼叫多個sql時),希望可以傳參到sql檔案中 方法:本例以sh呼叫hive命令執行sql為例,道理相同 1、sh 檔案中執行hive -f 命令 將inputdate傳給sql檔案使用

Linux 上Shell 指令碼執行的一個問題(^M)

為了更好更實時的檢視Linux系統的資源消耗,在網上找到了一個別人寫的Shell,參考 https://www.cnblogs.com/xianhaiyuan/p/6323599.html , 存成檔案後,通過FillZilla傳遞到Linux伺服器上,不了執行時候遇到報錯

[Scala Shell指令碼執行]

    Scala語言來自於Scalable(可伸縮的),既可以寫一些小的指令碼,又可以寫一寫複雜的伺服器端的程式。scala支援原始檔解釋執行,jar執行,各有利弊。本文主要介紹咋Linux系統中Scala的Shell指令碼,Scala原始檔,Scala的Jar執行方法。一

Shell指令碼執行超時怎麼辦?

在Shell裡會有一種不太常見的情況,就是指令碼有時候會出現超時的現象。一般來說遇到這種問題,我們都會簡單粗暴的採用下面這種指令碼來當“超時看門狗“: 這個指令碼搭配兩個變數使用的話,監控一點小程式碼還算OK,但是它的邏輯還是比較粗糙,比如如果在這個指令碼執行的時候,又有了一個新的process

提高sql語句執行效率及索引

(1)選擇最有效率的表名順序(只在基於規則的優化器中有效):ORACLE的解析器按照從右到左的順序處理FROM子句中的表名,FROM子句中寫在最後的表(基礎表 driving table)將被最先處理,在FROM子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表。如果有3個以上的表連線查詢,那就