利用python實現批量插入列印資訊的方法
使用列印資訊是除錯程式的必備手段,但是面對下面兩種情況:
1.如果程式程式碼/原始檔較多,而一時之間又無法確定問題範圍,這個時候可能需要在多個檔案插入列印資訊
2.剛剛接手維護大型模組,想要了解執行流程,需要在有可能執行到的地方都加入trace
上述情況,如果手動在每個函式的開頭,每個判斷語句都加入trace,會耗費很多時間
python是處理資料的一個好工具,結合正則表示式,我們可以寫一個指令碼,實現:
在每個函式/每個判斷的入口列印函式名/判斷的內容,當然前提是必須是在{}內
同時,我們要避免在迴圈語句內列印
下面是指令碼:
import re
import os
import os.path
rootpath = ''#程式碼所在的路徑
filterList = [];#要過濾的檔案
needFilter = False
for parent, dirs, files in os.walk(rootpath):
for f in files:
for idx in range(len(filterList)):
if f == filterList[idx]:
needFilter = True
break
if needFilter == True :
needFilter = False
continue
fname = os.path.splitext(f)#將檔名和字尾名分離
if fname[1] == '.c' or fname[1] == '.cpp':
fd = open(os.path.join(parent,f))
code = fd.read()
fd.close()
newStr1 = re.sub(r'(\S+)(.*)(\()(.*)(\))(\s*)([^\{|\}|\(|\)]*)(\s*)(\{)(?!\})(\s*)' ,r'\1\2\3\4\5\6\7\8\9\10printf("%s,%d,\1\2\3\4\5\\n",__FILE__,__LINE__);\10',code)
newStr2 = re.sub(r'(//)(.*)(\{)(\s*)printf\("%s,%d,\S+.*\(.*\)\\n",__FILE__,__LINE__\);',r'\1\2\3\4\1printf("%s,%d,\S+.*\(.*\)\\n",__FILE__,__LINE__);',newStr1)
newStr3 = re.sub(r'(=+)(\s*)(\{)(\s*)printf\("%s,%d,\S+.*\(.*\)\\n",__FILE__,__LINE__\);',r'\1\2\3\4',newStr2)
newStr4 = re.sub(r'(.*(for|while)\s*\([^\{]*\{)\s*printf\("%s,%d,\S+.*\(.*\)\\n",__FILE__,__LINE__\);',r'\1',newStr3)
newStr5 = re.sub(r'(.*(enum|extern)(.*|\s*)\{)\s*printf\("%s,%d,\S+.*\(.*\)\\n",__FILE__,__LINE__\);',r'\1',newStr4)
newStr6 = re.sub(r'struct(\s*)(.*)(\s*)(\{)(\s*)printf\("%s,%d,\S+.*\(.*\)\\n",__FILE__,__LINE__\);(\s*)',r'struct\1\2\3\4\5',newStr5)
newStr7 = re.sub(r'(\(\")(.*[^\\])\"(.*[^\\])\"(.*\",)',r'\1\2\\"\3\\"\4',newStr6)
newStr8 = newStr7
while True:
newStr7 = re.sub(r'(\(\")(.*[^\\])\"(.*[^\\])\"(.*\",)',r'\1\2\\"\3\\"\4',newStr7)
if newStr8 == newStr7:
break
newStr8 = newStr7
fd=file(os.path.join(parent,f),"w+")
fd.write(newStr8)
fd.close()
執行這個指令碼後,會遍歷rootpath下的所有檔案(包括子目錄下的檔案),然後過濾掉我們指定的檔案,要過濾的檔案放在 filterList 裡,過濾掉後,在剩下的檔案裡,篩選出C/C++原始檔,對每個篩選出來的檔案,進行以下操作:
1.從檔案讀取出程式碼
2.利用正則表示式新增trace
3.將程式碼寫回檔案
python對檔案的遍歷十分方便,這裡最主要的就是對正則表示式的使用,下面解釋每條正則表示式的作用:
1.
查詢:(\S+)(.)(()(.)())(\s*)([^{|}|(|)])(\s)({)(?!})(\s*)
替換為:\1\2\3\4\5\6\7\8\9\10printf(“%s,%d,\1\2\3\4\5\n”,FILE,LINE);\10
效果:
下面的程式碼
{
…
}
會被替換成
{
printf(“%s,%d\n”,FILE,LINE);
…
}
2.
查詢:(//)(.)({)(\s)printf(“%s,%d,\S+.(.)\n”,FILE,LINE);
替換為:\1\2\3\4\1printf(“%s,%d,\S+.(.)\n”,FILE,LINE);
效果:
下面的程式碼
//…{
printf(“%s,%d\n”,FILE,LINE);
會被替換成
//…{
// printf(“%s,%d\n”,FILE,LINE);
這是為了在被消除的程式碼後面加上的列印,否則會有編譯錯誤
3.
查詢:(=+)(\s*)({)(\s*)printf(“%s,%d,\S+.(.)\n”,FILE,LINE);
替換為:\1\2\3\4
效果:
={printf(“%s,%d\n”,FILE,LINE);
會被替換成
={
當=後跟隨著{表明是賦值操作,列印需要去掉
4.
查詢:(.(for|while)\s([^{]{)\s*printf(“%s,%d,\S+.(.*)\n”,FILE,LINE);
替換為:\1
效果:
把while和for迴圈裡的列印語句去掉
5.
查詢:(.(enum|extern)(.|\s*){)\s*printf(“%s,%d,\S+.(.)\n”,FILE,LINE);
替換為:\1
效果:
去掉列舉和extern裡的列印語句
6.
查詢:struct(\s*)(.)(\s)({)(\s*)printf(“%s,%d,\S+.(.)\n”,FILE,LINE);(\s*)
替換為:struct\1\2\3\4\5
效果:
去掉結構體定義裡的列印語句
7.
查詢:((\”)(.[^\])\”(.[^\])\”(.*\”,)
替換為:\1\2\”\3\”\4
效果:
因為插入的列印語句會把{前的一行內容打印出來,考慮下面的情況
if(a == “test”)
插入列印語句後
if(a == “test”){
printf(“%s,%d,a == “test”\n”,FILE,LINE);
由於”“沒有被轉義,所以編譯會出錯,上面的正則替換就是為了增加轉義,增加後:
if(a == “test”){
printf(“%s,%d,a == \”test\”\n”,FILE,LINE);
使用迴圈是為了應對修改的資料裡有多組”“,當處理結果和上次得到的結果一樣時,說明每對”“都已經被處理完,加上了轉移字元
如果發現自己的程式碼還有特殊的情況要處理,可以利用正則表示式繼續處理,一般C/C++的情況上面應該可以覆蓋