在字符串中显示不可打印的字符

时间:2012-12-18 07:00:14

标签: python python-3.x escaping

是否可以使用十六进制值可视化python字符串中的不可打印字符?

e.g。如果我有一个带换行符的字符串,我想用\x0a替换它。

我知道repr()会给我\n,但我正在寻找十六进制版本。

6 个答案:

答案 0 :(得分:13)

我不知道任何内置方法,但使用理解相当容易:

import string
printable = string.ascii_letters + string.digits + string.punctuation + ' '
def hex_escape(s):
    return ''.join(c if c in printable else r'\x{0:02x}'.format(ord(c)) for c in s)

答案 1 :(得分:8)

我有点迟到了,但是如果你需要它进行简单的调试,我发现这很有用:

string = "\n\t\nHELLO\n\t\n\a\17"

procd = [c for c in string]

print(procd)

# Prints ['\n,', '\t,', '\n,', 'H,', 'E,', 'L,', 'L,', 'O,', '\n,', '\t,', '\n,', '\x07,', '\x0f,']

丑陋,但它帮助我在字符串中找到不可打印的字符。

答案 2 :(得分:4)

您必须手动进行翻译;例如,使用正则表达式遍历字符串,并使用十六进制等效项替换每个匹配项。

import re

replchars = re.compile(r'[\n\r]')
def replchars_to_hex(match):
    return r'\x{0:02x}'.format(ord(match.group()))

replchars.sub(replchars_to_hex, inputtext)

以上示例仅匹配换行符和回车符,但您可以展开匹配的字符,包括使用\x转义码和范围。

>>> inputtext = 'Some example containing a newline.\nRight there.\n'
>>> replchars.sub(replchars_to_hex, inputtext)
'Some example containing a newline.\\x0aRight there.\\x0a'
>>> print(replchars.sub(replchars_to_hex, inputtext))
Some example containing a newline.\x0aRight there.\x0a

答案 3 :(得分:2)

修改ecatmur的解决方案以处理不可打印的非ASCII字符使其变得不那么琐碎且更令人讨厌:

def escape(c):
    if c.printable():
        return c
    c = ord(c)
    if c <= 0xff:
        return r'\x{0:02x}'.format(c)
    elif c <= '\uffff':
        return r'\u{0:04x}'.format(c)
    else:
        return r'\U{0:08x}'.format(c)

def hex_escape(s):
    return ''.join(escape(c) for c in s)

当然,如果str.isprintable不是您想要的定义,您可以编写不同的函数。 (请注意,它与string.printable中的内容完全不同 - 除了处理非ASCII可打印和不可打印的字符外,它还会考虑\n\r\t\x0b\x0c为不可打印的。

你可以让它更紧凑;这只是为了显示处理Unicode字符串所涉及的所有步骤。例如:

def escape(c):
    if c.printable():
        return c
    elif c <= '\xff':
        return r'\x{0:02x}'.format(ord(c))
    else:
        return c.encode('unicode_escape').decode('ascii')

真的,无论你做什么,你都必须明确地处理\r\n\t,因为所有的内置函数和stdlib函数我都是知道将通过那些特殊序列而不是它们的十六进制版本来逃避它们。

答案 4 :(得分:0)

我通过使用自定义str方法派生__repr__()子类做了类似的事情。这不完全是你想要的,但可能会给你一些想法。

# -*- coding: iso-8859-1 -*-

# special string subclass to override the default
# representation method. main purpose is to
# prefer using double quotes and avoid hex
# representation on chars with an ord > 128
class MsgStr(str):
    def __repr__(self):
        # use double quotes unless there are more of them within the string than
        # single quotes
        if self.count("'") >= self.count('"'):
            quotechar = '"'
        else:
            quotechar = "'"

        rep = [quotechar]
        for ch in self:
            # control char?
            if ord(ch) < ord(' '):
                # remove the single quotes around the escaped representation
                rep += repr(str(ch)).strip("'")
            # embedded quote matching quotechar being used?
            elif ch == quotechar:
                rep += "\\"
                rep += ch
            # else just use others as they are
            else:
                rep += ch
        rep += quotechar

        return "".join(rep)

if __name__ == "__main__":
    s1 = '\tWürttemberg'
    s2 = MsgStr(s1)
    print "str    s1:", s1
    print "MsgStr s2:", s2
    print "--only the next two should differ--"
    print "repr(s1):", repr(s1), "# uses built-in string 'repr'"
    print "repr(s2):", repr(s2), "# uses custom MsgStr 'repr'"
    print "str(s1):", str(s1)
    print "str(s2):", str(s2)
    print "repr(str(s1)):", repr(str(s1))
    print "repr(str(s2)):", repr(str(s2))
    print "MsgStr(repr(MsgStr('\tWürttemberg'))):", MsgStr(repr(MsgStr('\tWürttemberg')))

答案 5 :(得分:0)

还有一种方法可以打印不可打印的字符,即使它们在字符串中不可见(透明),也可以作为命令在字符串中执行,并且可以通过使用以下方法测量字符串的长度来观察它们的存在: len,也可以简单地将鼠标光标放在字符串的开头,然后查看/计算从开始到结束必须点击箭头键的次数,因为奇怪的是某些单个字符的长度以3为例,这似乎很困惑。 (不确定是否已经在先前的答案中证明了这一点)

在下面的示例屏幕截图中,我粘贴了一个具有特定结构和格式的135位字符串(必须预先为某些位位置及其总长度手动创建),以便将其解释为特定的ascii我正在运行的程序,并且在生成的打印字符串中包含不可打印的字符,例如'换行符'会导致换行符(更正:换页,我指的是新页面,而不是换行中断)在打印输出中,在打印结果之间有一个多余的空白行(请参见下文):

Example of printing non-printable characters that appear in printed string

Input a string:100100001010000000111000101000101000111011001110001000100001100010111010010101101011100001011000111011001000101001000010011101001000000
HPQGg]+\,vE!:@
>>> len('HPQGg]+\,vE!:@')
17
>>>

在上面的代码摘录中,尝试直接从该站点复制粘贴字符串HPQGg]+\,vE!:@,并查看将其粘贴到Python IDLE中会发生什么情况。

提示::您必须点按三次箭头/光标,才能浏览从PQ的两个字母,即使它们彼此相邻出现,例如它们之间实际上有一个File Separator ascii命令。

但是,即使在将其解码为十六进制的字节数组时获得相同的起始值,如果将十六进制转换回字节,它们看起来也会有所不同(也许缺少编码,不确定),但是无论哪种方式,上面的输出的程序会打印不可打印的字符(我在尝试开发压缩方法/实验时偶然遇到了这个问题。)

>>> bytes(b'HPQGg]+\,vE!:@').hex()
'48501c514767110c5d2b5c2c7645213a40'
>>> bytes.fromhex('48501c514767110c5d2b5c2c7645213a40')
b'HP\x1cQGg\x11\x0c]+\\,vE!:@'

>>> (0x48501c514767110c5d2b5c2c7645213a40 == 0b100100001010000000111000101000101000111011001110001000100001100010111010010101101011100001011000111011001000101001000010011101001000000)
True
>>> 

在上述135位字符串中,从大端开始的前16组8位编码每个字符(包括不可打印的字符),而最后7位组则产生@符号,如下所示:

Technical breakdown of the format of the above 135-bit string

下面是135位字符串的细目:

10010000 = H (72)
10100000 = P (80)
00111000 = x1c (28 for File Separator) *
10100010 = Q (81)
10001110 = G(71)
11001110 = g (103)
00100010 = x11 (17 for Device Control 1) *
00011000 = x0c (12 for NP form feed, new page) *
10111010 = ] (93 for right bracket ‘]’
01010110 = + (43 for + sign)
10111000 = \ (92 for backslash)
01011000  = , (44 for comma, ‘,’)
11101100  = v (118)
10001010 = E (69)
01000010 = ! (33 for exclamation)
01110100 = : (58  for colon ‘:’)
1000000  =  @ (64 for ‘@’ sign)

因此,最后,在上方的字节数组中,有关将不可打印的内容显示为十六进制的子问题的答案出现了字母x1c,它表示文件分隔符命令,提示中也已指出。如果不包含左侧的前缀b,则可以将字节数组视为字符串,并且该值再次显示在打印字符串中,尽管它是不可见的(尽管可以通过上面的提示和{观察到) {1}}命令)。