程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> Python >> Python實現Linux命令xxd -i功能

Python實現Linux命令xxd -i功能

編輯:Python

Python實現Linux命令xxd -i功能

標簽: Python xxd


[TOC]

聲明

本文同時也發布於作業部落,閱讀體驗可能更好。

一. Linux xxd -i功能

Linux系統xxd命令使用二進制或十六進制格式顯示文件內容。若未指定outfile參數,則將結果顯示在終端屏幕上;否則輸出到outfile中。詳細的用法可參考linux命令xxd。

本文主要關注xxd命令-i選項。使用該選項可輸出以inputfile為名的C語言數組定義。例如,執行echo 12345 > testxxd -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)即可控制輸出位寬。只是,再加完善難免提高代碼復雜度,也許會事倍功半。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved