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实现的检测web服务器健康状况的小程序
Sep 17 Python
python常用知识梳理(必看篇)
Mar 23 Python
python方向键控制上下左右代码
Jan 20 Python
10款最好的Python开发编辑器
Jul 03 Python
python 使用plt画图,去除图片四周的白边方法
Jul 09 Python
PyQt5通信机制 信号与槽详解
Aug 07 Python
python GUI库图形界面开发之PyQt5多线程中信号与槽的详细使用方法与实例
Mar 08 Python
python类共享变量操作
Sep 03 Python
详解python os.path.exists判断文件或文件夹是否存在
Nov 16 Python
使用豆瓣源来安装python中的第三方库方法
Jan 26 Python
Python3利用openpyxl读写Excel文件的方法实例
Feb 03 Python
python模块内置属性概念及实例
Feb 18 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中用foreach来操作数组的代码
2011/07/17 PHP
PHP curl 抓取AJAX异步内容示例
2014/09/09 PHP
laravel 5 实现模板主题功能(续)
2015/03/02 PHP
PHP读取mssql json数据中文乱码的解决办法
2016/04/11 PHP
基于Codeigniter框架实现的student信息系统站点动态发布功能详解
2017/03/23 PHP
thinkphp框架实现路由重定义简化url访问地址的方法分析
2020/04/04 PHP
BOOM vs RR BO5 第二场 2.14
2021/03/10 DOTA
JQuery异步加载无限下拉框级联功能实现示例
2014/02/19 Javascript
jQuery的animate函数学习记录
2014/08/08 Javascript
jquery实现适用于门户站的导航下拉菜单效果代码
2015/08/24 Javascript
Javascript删除指定元素节点的方法
2016/06/21 Javascript
用原生js统计文本行数的简单示例
2016/08/19 Javascript
基于Node.js的WebSocket通信实现
2017/03/11 Javascript
vue中Npm run build 根据环境传递参数方法来打包不同域名
2018/03/29 Javascript
Vue请求JSON Server服务器数据的实现方法
2018/11/02 Javascript
vue如何根据网站路由判断页面主题色详解
2018/11/02 Javascript
python单例模式实例分析
2015/04/08 Python
python将字典内容存入mysql实例代码
2018/01/18 Python
Python获取航线信息并且制作成图的讲解
2019/01/03 Python
Python 使用 PyQt5 开发的关机小工具分享
2020/07/16 Python
Python中logging日志记录到文件及自动分割的操作代码
2020/08/05 Python
HTML5之语义标签介绍
2016/07/07 HTML / CSS
解决canvas转base64/jpeg时透明区域变成黑色背景的方法
2016/10/23 HTML / CSS
SteelSeries赛睿官网:游戏外设和配件的领先制造商(耳机、键盘、鼠标和鼠标垫)
2018/06/17 全球购物
优衣库台湾官网:UNIQLO台湾
2019/02/01 全球购物
Hotels.com日本:国外和海外住宿,酒店预订
2019/12/13 全球购物
总裁岗位职责
2013/12/04 职场文书
园林资料员岗位职责
2013/12/30 职场文书
军训自我鉴定
2014/01/22 职场文书
农业开发项目建议书
2014/05/16 职场文书
反对邪教标语
2014/06/30 职场文书
商务经理岗位职责
2014/07/30 职场文书
销售团队获奖感言
2014/08/14 职场文书
房地产销售员岗位职责
2015/04/11 职场文书
欠款起诉书范文
2015/05/19 职场文书
基于Python实现股票收益率分析
2022/04/02 Python