一. Linux xxd -i功能
Linux系統xxd指令使用二進位或十六進位格式顯示檔案內容。若未指定outfile參數,則將結果顯示在終端機畫面上;否則輸出至outfile中。詳細的用法可參考linux指令xxd。
本文主要關注xxd指令-i選項。使用此選項可輸出以inputfile為名的C語言陣列定義。例如,執行echo 12345 > test和xxd -i test指令後,輸出為:
unsigned char test[] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x0a }; unsigned int test_len = 6;
可見,陣列名稱即輸入檔名(若有後綴名則點號替換為底線)。請注意,0x0a表示換行符LF,即'\n'。
二. xxd -i常見用途
#當裝置沒有檔案系統或不支援動態記憶體管理時,有時會將二進位檔案(如引導程式和韌體)內容儲存在C程式碼靜態數組內。此時,借助xxd指令就可自動產生版本陣列。舉例如下:
1) 使用Linux指令xdd將二進位檔案VdslBooter.bin轉換為16進位檔案DslBooter.txt:
xxd -i 31479547a2d967270a9bebad8dc6c6a7 DslBooter.txt
其中,'-i'選項表示輸出為C包含檔案的風格(陣列方式)。重定向符號'2f460f9bd7059691c11a21c2a688d785python xxdi.py,執行結果為True。
接下來的程式碼片段將省略頭部的腳本和編碼聲明,以及尾部的'main'段。 產生C數組前,應確保數組名合法。 C語言識別碼只能由字母、數字和底線組成,且不能以數字開頭。此外,關鍵字不能用作標識符。所有,需要對非法字元做處理,其規則請參考程式碼註解:import re def GenerateCArrayName(inFile): #字母数字下划线以外的字符均转为下划线 #'int $=5;'的定义在Gcc 4.1.2可编译通过,但此处仍视为非法标识符 inFile = re.sub('[^0-9a-zA-Z\_]', '_', inFile) #'_'改为''可剔除非法字符 #数字开头加双下划线 if inFile[0].isdigit() == True: inFile = '__' + inFile #若输入文件名为C语言关键字,则将其大写并加下划线后缀作为数组名 #不能仅仅大写或加下划线前,否则易于用户自定义名冲突 if IsCKeywords(inFile) is True: inFile = '%s_' %inFile.upper() return inFile以print GenerateCArrayName('1a$if1#1_4.txt')執行時,入參字串將會轉換為__1a_if1_1_4_txt。類似地,_Bool被轉換為_BOOL_。 為了盡可能模擬Linux指令風格,還需提供命令列選項和參數。解析模組選用optionparser,其用法詳見python命令列解析。類別xxd -i功能的命令列實作如下:
#def ParseOption(base, cols, strip, inFile, outFile): def ParseOption(base = 16, cols = 12, strip = False, inFile = '', outFile = None): from optparse import OptionParser custUsage = '\n xxdi(.py) [options] inFile [outFile]' parser = OptionParser(usage=custUsage) parser.add_option('-b', '--base', dest='base', help='represent values according to BASE(default:16)') parser.add_option('-c', '--column', dest='col', help='COL octets per line(default:12)') parser.add_option('-s', '--strip', action='store_true', dest='strip', help='only output C array elements') (options, args) = parser.parse_args() if options.base is not None: base = int(options.base) if options.col is not None: cols = int(options.col) if options.strip is not None: strip = True if len(args) == 0: print 'No argument, at least one(inFile)!\nUsage:%s' %custUsage if len(args) >= 1: inFile = args[0] if len(args) >= 2: outFile = args[1] return ([base, cols, strip], [inFile, outFile])#被註解掉的def ParseOption(...)原本是以下面的方式呼叫:
base = 16; cols = 12; strip = False; inFile = ''; outFile = '' ([base, cols, strip], [inFile, outFile]) = ParseOption(base, cols, strip, inFile, outFile)其意圖是同時修改base、cols、strip等參數值。但這種寫法非常彆扭,改用預設參數的函數定義方式,呼叫時只需要寫ParseOption()即可。若讀者知道更好的寫法,望不吝賜教。 以-h選項調出指令提示,可見非常接近Linux風格:
E:\PyTest>python xxdi.py -h Usage: xxdi(.py) [options] inFile [outFile] Options: -h, --help show this help message and exit -b BASE, --base=BASE represent values according to BASE(default:16) -c COL, --column=COL COL octets per line(default:12) -s, --strip only output C array elements基於上述練習,接著完成本文的重頭戲:
def Xxdi(): #解析命令行选项及参数 ([base, cols, strip], [inFile, outFile]) = ParseOption() import os if os.path.isfile(inFile) is False: print ''''%s' is not a file!''' %inFile return with open(inFile, 'rb') as file: #必须以'b'模式访问二进制文件 #file = open(inFile, 'rb') #Python2.5以下版本不支持with...as语法 #if True: #不用for line in file或readline(s),以免遇'0x0a'换行 content = file.read() #将文件内容"打散"为字节数组 if base is 16: #Hexadecimal content = map(lambda x: hex(ord(x)), content) elif base is 10: #Decimal content = map(lambda x: str(ord(x)), content) elif base is 8: #Octal content = map(lambda x: oct(ord(x)), content) else: print '[%s]: Invalid base or radix for C language!' %base return #构造数组定义头及长度变量 cArrayName = GenerateCArrayName(inFile) if strip is False: cArrayHeader = 'unsigned char %s[] = {' %cArrayName else: cArrayHeader = '' cArrayTailer = '};\nunsigned int %s_len = %d;' %(cArrayName, len(content)) if strip is True: cArrayTailer = '' #print会在每行输出后自动换行 if outFile is None: print cArrayHeader for i in range(0, len(content), cols): line = ', '.join(content[i:i+cols]) print ' ' + line + ',' print cArrayTailer return with open(outFile, 'w') as file: #file = open(outFile, 'w') #Python2.5以下版本不支持with...as语法 #if True: file.write(cArrayHeader + '\n') for i in range(0, len(content), cols): line = reduce(lambda x,y: ', '.join([x,y]), content[i:i+cols]) file.write(' %s,\n' %line) file.flush() file.write(cArrayTailer)Python2.5以下版本不支援with...as語法,而作者除錯所使用的Linux系統只裝有Python2.4.3。因此,要在Linux系統中運行xddi.py,只能寫為file = open(...。但這需要處理文件的關閉和異常,詳見理解Python中的with…as…語法。注意,Python2. 5中使用with...as語法時需要宣告from __future__ import with_statement。 經過Windows和Linux系統雙重檢驗後,Xddi()工作基本上符合預期。 #
import platform #判断Python是否为major.minor及以上版本 def IsForwardPyVersion(major, minor): #python_version()返回'major.minor.patchlevel',如'2.7.11' ver = platform.python_version().split('.') if int(ver[0]) >= major and int(ver[1]) >= minor: return True return False
再以稍大的二級製檔為例,執行python xxdi.py VdslBooter.bin booter.c後,booter.c檔內容如下(截取首尾):
E:\PyTest>python xxdi.py -c 5 -b 2 -s 123456789ABCDEF.txt [2]: Invalid base or radix for C language! E:\Pytest>python xxdi.py -c 5 -b 10 -s 123456789ABCDEF.txt 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, E:\PyTest>python xxdi.py -c 5 -b 10 123456789ABCDEF.txt unsigned char __123456789ABCDEF_txt[] = { 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, }; unsigned int __123456789ABCDEF_txt_len = 15; E:\PyTest>python xxdi.py -c 5 -b 8 123456789ABCDEF.txt unsigned char __123456789ABCDEF_txt[] = { 061, 062, 063, 064, 065, 066, 067, 070, 071, 0101, 0102, 0103, 0104, 0105, 0106, }; unsigned int __123456789ABCDEF_txt_len = 15; E:\PyTest>python xxdi.py 123456789ABCDEF.txt unsigned char __123456789ABCDEF_txt[] = { 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, }; unsigned int __123456789ABCDEF_txt_len = 15;#
綜上可見,作者實現的xxdi模組與Linux xxd -i功能非常接近,且各有優劣。 xxdi優點在於對陣列名合法性校驗更充分(關鍵字檢查),陣列內容表現形式更豐富(8進位和10進位);缺點在於不支援重定向,且數值寬度不固定(如0xb和0xff)。當然,這些缺點並不難消除。例如,以'0x%02x'%val取代hex(val)即可控制輸出位寬。只是,再加完善難免提高程式碼複雜度,也許會事倍功半。
以上所述是小編給大家介紹的Python實作Linux指令xxd -i功能,希望對大家以上幫忙!
更多Python實作Linux指令xxd -i功能介紹相關文章請關注PHP中文網!