17

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

例如,如果我有一个带有换行符的字符串,我想用\x0a.

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

4

6 回答 6

17

我不知道任何内置方法,但使用理解很容易做到:

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)
于 2012-12-18T14:55:15.443 回答
15

我参加聚会有点晚了,但是如果您需要它来进行简单的调试,我发现这很有效:

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,']

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

于 2015-03-01T00:58:17.703 回答
10

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

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
于 2012-12-18T07:10:16.147 回答
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 函数都将通过这些特殊序列而不是它们的十六进制版本来转义它们。

于 2012-12-18T21:56:15.427 回答
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')))
于 2012-12-18T12:24:51.533 回答
0

还有一种方法可以打印不可打印的字符,即使它们在字符串中作为命令执行,即使在字符串中不可见(透明),也可以通过使用lenas测量字符串的长度来观察它们的存在只需将鼠标光标放在字符串的开头并查看/计算您必须点击箭头键多少次才能从开始到结束,因为奇怪的是,例如某些单个字符的长度可能为 3,这似乎令人困惑. (不确定这是否已经在之前的答案中得到证明)

在下面的示例屏幕截图中,我粘贴了一个 135 位字符串,该字符串具有特定的结构和格式(我必须事先为某些位位置及其总长度手动创建),以便特定程序将其解释为 ascii。 m 正在运行,并且在生成的打印字符串中是不可打印的字符,例如“换行符”,它实际上会导致打印输出中的换行符(更正:换页,我的意思是新页面,而不是换行符)打印结果之间的整个空白行(见下文):

打印出现在打印字符串中的不可打印字符的示例

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

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

提示:您必须点击箭头/光标三次才能跨越两个字母 from PtoQ即使它们彼此相邻,因为它们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 位产生@符号,如下所示:

上述 135 位字符串格式的技术分解

这里的文本是 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如果不包括左侧的前缀,则字节数组可以被视为字符串,并且该值再次显示在打印字符串中,尽管它是不可见的(尽管可以通过上面的提示和len命令观察到它的存在)。

于 2019-08-22T20:45:35.127 回答