Python實作Linux指令xxd -i功能介紹

高洛峰
發布: 2017-03-07 15:58:16
原創
2351 人瀏覽過

一. 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 < VdslBooter.bin > DslBooter.txt

其中,'-i'選項表示輸出為C包含檔案的風格(陣列方式)。重定向符號'<'將VdslBooter.bin檔案內容重定向到標準輸入,該處理可剔除陣列宣告和長度變數定義,使輸出僅包含16進位數值。

2) 在C程式碼原始檔內定義對應的靜態陣列:

static const uint8 bootImageArray[] = {
#include " ../../DslBooter.txt"
};
TargetImage bootImage = {
(uint8 *) bootImageArray,
sizeof(bootImageArray) / sizeof(bootImageArray[0])
};
登入後複製

編譯原始碼時,DslBooter.txt檔案的內容會自動展開到上述數組內。透過巧用#include預處理指令,可免去手工拷貝數組內容的麻煩。

三. 類別xxd -i功能的Python實作

#本節將使用Python2.7語言實作類似xxd -i的功能。

因為作者處於學習階段,程式碼中存在許多寫法不同但功能相同或相近的地方,旨在提供不同的語法參考,敬請諒解。

首先,請看一段短小卻完整的程式(保存為xddi.py):

#
#!/usr/bin/python
#coding=utf-8
#判断是否C语言关键字
CKeywords = ("auto", "break", "case", "char", "const", "continue", "default",
"do","double","else","enum","extern","float","for",
"goto","if","int","long","register","return","short",
"signed","static","sizeof","struct","switch","typedef","union",
"unsigned","void","volatile","while", "_Bool") #_Bool为C99新关键字
def IsCKeywords(name):
for x in CKeywords:
if cmp(x, name) == 0:
return True
return False
if __name__ == &#39;__main__&#39;:
print IsCKeywords(&#39;const&#39;)
#Xxdi()
登入後複製

##這段程式碼判斷給定的字串是否為C語言關鍵字。在Windows系統cmd命令提示字元下輸入E:\PyTest>python xxdi.py,執行結果為True。

接下來的程式碼片段將省略頭部的腳本和編碼聲明,以及尾部的'main'段。

產生C數組前,應確保數組名合法。 C語言識別碼只能由字母、數字和底線組成,且不能以數字開頭。此外,關鍵字不能用作標識符。所有,需要對非法字元做處理,其規則請參考程式碼註解:

import re
def GenerateCArrayName(inFile):
#字母数字下划线以外的字符均转为下划线
#&#39;int $=5;&#39;的定义在Gcc 4.1.2可编译通过,但此处仍视为非法标识符
inFile = re.sub(&#39;[^0-9a-zA-Z\_]&#39;, &#39;_&#39;, inFile) #&#39;_&#39;改为&#39;&#39;可剔除非法字符
#数字开头加双下划线
if inFile[0].isdigit() == True:
inFile = &#39;__&#39; + inFile
#若输入文件名为C语言关键字,则将其大写并加下划线后缀作为数组名
#不能仅仅大写或加下划线前,否则易于用户自定义名冲突
if IsCKeywords(inFile) is True:
inFile = &#39;%s_&#39; %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 = &#39;&#39;, outFile = None):
from optparse import OptionParser
custUsage = &#39;\n xxdi(.py) [options] inFile [outFile]&#39;
parser = OptionParser(usage=custUsage)
parser.add_option(&#39;-b&#39;, &#39;--base&#39;, dest=&#39;base&#39;,
help=&#39;represent values according to BASE(default:16)&#39;)
parser.add_option(&#39;-c&#39;, &#39;--column&#39;, dest=&#39;col&#39;,
help=&#39;COL octets per line(default:12)&#39;)
parser.add_option(&#39;-s&#39;, &#39;--strip&#39;, action=&#39;store_true&#39;, dest=&#39;strip&#39;,
help=&#39;only output C array elements&#39;)
(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 &#39;No argument, at least one(inFile)!\nUsage:%s&#39; %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 = &#39;&#39;; outFile = &#39;&#39;
([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 &#39;&#39;&#39;&#39;%s&#39; is not a file!&#39;&#39;&#39; %inFile
return
with open(inFile, &#39;rb&#39;) as file: #必须以&#39;b&#39;模式访问二进制文件
#file = open(inFile, &#39;rb&#39;) #Python2.5以下版本不支持with...as语法
#if True:
#不用for line in file或readline(s),以免遇&#39;0x0a&#39;换行
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 &#39;[%s]: Invalid base or radix for C language!&#39; %base
return
#构造数组定义头及长度变量
cArrayName = GenerateCArrayName(inFile)
if strip is False:
cArrayHeader = &#39;unsigned char %s[] = {&#39; %cArrayName
else:
cArrayHeader = &#39;&#39;
cArrayTailer = &#39;};\nunsigned int %s_len = %d;&#39; %(cArrayName, len(content))
if strip is True: cArrayTailer = &#39;&#39;
#print会在每行输出后自动换行
if outFile is None:
print cArrayHeader
for i in range(0, len(content), cols):
line = &#39;, &#39;.join(content[i:i+cols])
print &#39; &#39; + line + &#39;,&#39;
print cArrayTailer
return
with open(outFile, &#39;w&#39;) as file:
#file = open(outFile, &#39;w&#39;) #Python2.5以下版本不支持with...as语法
#if True:
file.write(cArrayHeader + &#39;\n&#39;)
for i in range(0, len(content), cols):
line = reduce(lambda x,y: &#39;, &#39;.join([x,y]), content[i:i+cols])
file.write(&#39; %s,\n&#39; %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()返回&#39;major.minor.patchlevel&#39;,如&#39;2.7.11&#39;
ver = platform.python_version().split(&#39;.&#39;)
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中文網!

相關標籤:
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板
關於我們 免責聲明 Sitemap
PHP中文網:公益線上PHP培訓,幫助PHP學習者快速成長!