Python实现Linux命令xxd -i功能


Posted in Python onMarch 06, 2016

一. 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__ == '__main__':
print IsCKeywords('const')
#Xxdi()

这段代码判断给定的字符串是否为C语言关键字。在Windows系统cmd命令提示符下输入E:\PyTest>python 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。

可通过platform.python_version()获取Python版本号。例如:

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

经过Windows和Linux系统双重检验后,Xddi()工作基本符合预期。以123456789ABCDEF.txt文件(内容为'123456789ABCDEF')为例,测试结果如下:

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;

再以稍大的二级制文件为例,执行 python xxdi.py VdslBooter.bin booter.c后,booter.c文件内容如下(截取首尾):

unsigned char VdslBooter_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,
};
unsigned int VdslBooter_bin_len = 53588;

综上可见,作者实现的xxdi模块与Linux xxd -i功能非常接近,且各有优劣。xxdi优点在于对数组名合法性校验更充分(关键字检查),数组内容表现形式更丰富(8进制和10进制);缺点在于不支持重定向,且数值宽度不固定(如0xb和0xff)。当然,这些缺点并不难消除。例如,用'0x%02x'%val代替hex(val)即可控制输出位宽。只是,再加完善难免提高代码复杂度,也许会事倍功半。

以上所述是小编给大家介绍的Python实现Linux命令xxd -i功能,希望对大家以上帮助!

Python 相关文章推荐
Python中条件选择和循环语句使用方法介绍
Mar 13 Python
python中的多线程实例教程
Aug 27 Python
在Python中使用lambda高效操作列表的教程
Apr 24 Python
两个使用Python脚本操作文件的小示例分享
Aug 27 Python
浅析Python中元祖、列表和字典的区别
Aug 17 Python
浅谈Python数据类型判断及列表脚本操作
Nov 04 Python
python绘制简单折线图代码示例
Dec 19 Python
Python下载网络文本数据到本地内存的四种实现方法示例
Feb 05 Python
django模板加载静态文件的方法步骤
Mar 01 Python
在Pandas中处理NaN值的方法
Jun 25 Python
python操作redis数据库的三种方法
Sep 10 Python
利用Selenium添加cookie实现自动登录的示例代码(fofa)
May 08 Python
基于Python实现一个简单的银行转账操作
Mar 06 #Python
Python切片知识解析
Mar 06 #Python
Django Admin实现上传图片校验功能
Mar 06 #Python
python如何通过protobuf实现rpc
Mar 06 #Python
使用Python保存网页上的图片或者保存页面为截图
Mar 05 #Python
Python发送form-data请求及拼接form-data内容的方法
Mar 05 #Python
Python多线程爬虫简单示例
Mar 04 #Python
You might like
PHP 字符串分割和比较
2009/10/06 PHP
php如何解决无法上传大于8M的文件问题
2014/03/10 PHP
Codeigniter整合Tank Auth权限类库详解
2014/06/12 PHP
php+ajax实现无刷新分页的方法
2014/11/04 PHP
php实现数组中索引关联数据转换成json对象的方法
2015/07/08 PHP
PHP whois查询类定义与用法示例
2019/04/03 PHP
php设计模式之备忘模式分析【星际争霸游戏案例】
2020/03/24 PHP
让JavaScript拥有类似Lambda表达式编程能力的方法
2010/09/12 Javascript
Js 时间间隔计算的函数(间隔天数)
2011/11/15 Javascript
JS小功能(setInterval实现图片效果显示时间)实例代码
2013/11/28 Javascript
jQuery瀑布流插件Wookmark使用实例
2014/04/02 Javascript
批量修改标签css样式以input标签为例
2014/07/31 Javascript
js判断某个方法是否存在实例代码
2015/01/10 Javascript
JavaScript+html5 canvas制作色彩斑斓的正方形效果
2016/01/27 Javascript
jQuery插件FusionCharts实现的2D柱状图效果示例【附demo源码下载】
2017/03/06 Javascript
详解vue.js之props传递参数
2017/12/12 Javascript
JS实现的文字间歇循环滚动效果完整示例
2018/02/13 Javascript
Vue登录注册并保持登录状态的方法
2018/08/17 Javascript
Vuex mutitons和actions初使用详解
2019/03/04 Javascript
layui table单元格事件修改值的方法
2019/09/24 Javascript
layui: layer.open加载窗体时出现遮罩层的解决方法
2019/09/26 Javascript
vue 使用 vue-pdf 实现pdf在线预览的示例代码
2020/04/26 Javascript
VUE 单页面使用 echart 窗口变化时的用法
2020/07/30 Javascript
Python中的类与对象之描述符详解
2015/03/27 Python
Python工程师面试必备25条知识点
2018/01/17 Python
ubuntu安装mysql pycharm sublime
2018/02/20 Python
Python学习之路之pycharm的第一个项目搭建过程
2020/06/18 Python
python将数据插入数据库的代码分享
2020/08/16 Python
韩国11街:11STREET
2018/03/27 全球购物
医院护士的求职信范文
2013/12/26 职场文书
自主招生推荐信怎么写
2015/03/26 职场文书
初中体育教学随笔
2015/08/15 职场文书
2016年读书月活动总结范文
2016/04/06 职场文书
创业计划书之密室逃脱
2019/11/08 职场文书
springboot 启动如何排除某些bean的注入
2021/08/02 Java/Android
Oracle锁表解决方法的详细记录
2022/06/05 Oracle