Python实现Linux命令xxd -i功能
一.Linuxxxd-i功能
Linux系统xxd命令使用二进制或十六进制格式显示文件内容。若未指定outfile参数,则将结果显示在终端屏幕上;否则输出到outfile中。详细的用法可参考linux命令xxd。
本文主要关注xxd命令-i选项。使用该选项可输出以inputfile为名的C语言数组定义。例如,执行echo12345>test和xxd-itest命令后,输出为:
unsignedchartest[]={
0x31,0x32,0x33,0x34,0x35,0x0a
};
unsignedinttest_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代码源文件内定义相应的静态数组:
staticconstuint8bootImageArray[]={
#include"../../DslBooter.txt"
};
TargetImagebootImage={
(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新关键字
defIsCKeywords(name):
forxinCKeywords:
ifcmp(x,name)==0:
returnTrue
returnFalse
if__name__=='__main__':
printIsCKeywords('const')
#Xxdi()
这段代码判断给定的字符串是否为C语言关键字。在Windows系统cmd命令提示符下输入E:\PyTest>pythonxxdi.py,执行结果为True。
接下来的代码片段将省略头部的脚本和编码声明,以及尾部的'main'段。
生成C数组前,应确保数组名合法。C语言标识符只能由字母、数字和下划线组成,且不能以数字开头。此外,关键字不能用作标识符。所有,需要对非法字符做处理,其规则参见代码注释:
importre
defGenerateCArrayName(inFile):
#字母数字下划线以外的字符均转为下划线
#'int$=5;'的定义在Gcc4.1.2可编译通过,但此处仍视为非法标识符
inFile=re.sub('[^0-9a-zA-Z\_]','_',inFile)#'_'改为''可剔除非法字符
#数字开头加双下划线
ifinFile[0].isdigit()==True:
inFile='__'+inFile
#若输入文件名为C语言关键字,则将其大写并加下划线后缀作为数组名
#不能仅仅大写或加下划线前,否则易于用户自定义名冲突
ifIsCKeywords(inFile)isTrue:
inFile='%s_'%inFile.upper()
returninFile
以printGenerateCArrayName('1a$if1#1_4.txt')执行时,入参字符串将被转换为__1a_if1_1_4_txt。类似地,_Bool被转换为_BOOL_。
为了尽可能模拟Linux命令风格,还需提供命令行选项和参数。解析模块选用optionparser,其用法详见python命令行解析。类xxd-i功能的命令行实现如下:
#defParseOption(base,cols,strip,inFile,outFile):
defParseOption(base=16,cols=12,strip=False,inFile='',outFile=None):
fromoptparseimportOptionParser
custUsage='\nxxdi(.py)[options]inFile[outFile]'
parser=OptionParser(usage=custUsage)
parser.add_option('-b','--base',dest='base',
help='representvaluesaccordingtoBASE(default:16)')
parser.add_option('-c','--column',dest='col',
help='COLoctetsperline(default:12)')
parser.add_option('-s','--strip',action='store_true',dest='strip',
help='onlyoutputCarrayelements')
(options,args)=parser.parse_args()
ifoptions.baseisnotNone:
base=int(options.base)
ifoptions.colisnotNone:
cols=int(options.col)
ifoptions.stripisnotNone:
strip=True
iflen(args)==0:
print'Noargument,atleastone(inFile)!\nUsage:%s'%custUsage
iflen(args)>=1:
inFile=args[0]
iflen(args)>=2:
outFile=args[1]
return([base,cols,strip],[inFile,outFile])
被注释掉的defParseOption(...)原本是以下面的方式调用:
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>pythonxxdi.py-h Usage: xxdi(.py)[options]inFile[outFile] Options: -h,--helpshowthishelpmessageandexit -bBASE,--base=BASErepresentvaluesaccordingtoBASE(default:16) -cCOL,--column=COLCOLoctetsperline(default:12) -s,--striponlyoutputCarrayelements
基于上述练习,接着完成本文的重头戏:
defXxdi():
#解析命令行选项及参数
([base,cols,strip],[inFile,outFile])=ParseOption()
importos
ifos.path.isfile(inFile)isFalse:
print''''%s'isnotafile!'''%inFile
return
withopen(inFile,'rb')asfile:#必须以'b'模式访问二进制文件
#file=open(inFile,'rb')#Python2.5以下版本不支持with...as语法
#ifTrue:
#不用forlineinfile或readline(s),以免遇'0x0a'换行
content=file.read()
#将文件内容"打散"为字节数组
ifbaseis16:#Hexadecimal
content=map(lambdax:hex(ord(x)),content)
elifbaseis10:#Decimal
content=map(lambdax:str(ord(x)),content)
elifbaseis8:#Octal
content=map(lambdax:oct(ord(x)),content)
else:
print'[%s]:InvalidbaseorradixforClanguage!'%base
return
#构造数组定义头及长度变量
cArrayName=GenerateCArrayName(inFile)
ifstripisFalse:
cArrayHeader='unsignedchar%s[]={'%cArrayName
else:
cArrayHeader=''
cArrayTailer='};\nunsignedint%s_len=%d;'%(cArrayName,len(content))
ifstripisTrue:cArrayTailer=''
#print会在每行输出后自动换行
ifoutFileisNone:
printcArrayHeader
foriinrange(0,len(content),cols):
line=','.join(content[i:i+cols])
print''+line+','
printcArrayTailer
return
withopen(outFile,'w')asfile:
#file=open(outFile,'w')#Python2.5以下版本不支持with...as语法
#ifTrue:
file.write(cArrayHeader+'\n')
foriinrange(0,len(content),cols):
line=reduce(lambdax,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__importwith_statement。
可通过platform.python_version()获取Python版本号。例如:
importplatform
#判断Python是否为major.minor及以上版本
defIsForwardPyVersion(major,minor):
#python_version()返回'major.minor.patchlevel',如'2.7.11'
ver=platform.python_version().split('.')
ifint(ver[0])>=majorandint(ver[1])>=minor:
returnTrue
returnFalse
经过Windows和Linux系统双重检验后,Xddi()工作基本符合预期。以123456789ABCDEF.txt文件(内容为'123456789ABCDEF')为例,测试结果如下:
E:\PyTest>pythonxxdi.py-c5-b2-s123456789ABCDEF.txt
[2]:InvalidbaseorradixforClanguage!
E:\Pytest>pythonxxdi.py-c5-b10-s123456789ABCDEF.txt
49,50,51,52,53,
54,55,56,57,65,
66,67,68,69,70,
E:\PyTest>pythonxxdi.py-c5-b10123456789ABCDEF.txt
unsignedchar__123456789ABCDEF_txt[]={
49,50,51,52,53,
54,55,56,57,65,
66,67,68,69,70,
};
unsignedint__123456789ABCDEF_txt_len=15;
E:\PyTest>pythonxxdi.py-c5-b8123456789ABCDEF.txt
unsignedchar__123456789ABCDEF_txt[]={
061,062,063,064,065,
066,067,070,071,0101,
0102,0103,0104,0105,0106,
};
unsignedint__123456789ABCDEF_txt_len=15;
E:\PyTest>pythonxxdi.py123456789ABCDEF.txt
unsignedchar__123456789ABCDEF_txt[]={
0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x41,0x42,0x43,
0x44,0x45,0x46,
};
unsignedint__123456789ABCDEF_txt_len=15;
再以稍大的二级制文件为例,执行pythonxxdi.pyVdslBooter.binbooter.c后,booter.c文件内容如下(截取首尾):
unsignedcharVdslBooter_bin[]={
0xff,0x31,0x0,0xb,0xff,0x3,0x1f,0x5a,0x0,0x0,0x0,0x0,
//............
0x0,0x0,0x0,0x0,0xff,0xff,0x0,0x0,0x0,0x0,0x0,0x0,
0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
};
unsignedintVdslBooter_bin_len=53588;
综上可见,作者实现的xxdi模块与Linuxxxd-i功能非常接近,且各有优劣。xxdi优点在于对数组名合法性校验更充分(关键字检查),数组内容表现形式更丰富(8进制和10进制);缺点在于不支持重定向,且数值宽度不固定(如0xb和0xff)。当然,这些缺点并不难消除。例如,用'0x%02x'%val代替hex(val)即可控制输出位宽。只是,再加完善难免提高代码复杂度,也许会事倍功半。
以上所述是小编给大家介绍的Python实现Linux命令xxd-i功能,希望对大家以上帮助!