如何在Python中转义整个字符串?

1 投票
5 回答
2680 浏览
提问于 2025-04-18 07:22
file=r'D:\tdx\vipdoc\szf10\300383.Txt'
text=open(file,"r").read()

这个文件可以被读取,但一开始我写的 file 是:

file='D:\tdx\vipdoc\szf10\300383.Txt'

我不能用 text=open(file,"r").read() 来读取它。

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument: 'D:\tdx\x0bipdoc\\szf10\xc0383.Txt'

如果不使用 file=r'D:\tdx\vipdoc\szf10\300383.Txt',我该怎么办呢?

也许我需要用某种方法来处理整个 file 字符串?

问题是:文件在一开始就定义了,file 现在是一个包含字符串的变量,我只能在程序中调用它,我该如何在程序中修复这个问题。

  • 方法1: file=r'D:\tdx\vipdoc\szf10\300383.Txt' 不能使用。
  • 方法2: file='D:\\tdx\\vipdoc\\szf10\\300383.Txt' 也不能使用。

当程序已经在运行时,考虑到 file 是一个字符串变量,我现在该如何修复它呢?

假设 file 不是一个字符串字面量,而是从代码的其他部分传递过来的,我无法修复它以使用正确的格式,但仍然想使用这个文件名。

为什么我不能简单地用 file.replace("\","\\") 来替换 'D:\tdx\vipdoc\szf10\300383.Txt' 呢?

>>> file="D:\tdx\vipdoc\shf10\300383.Txt"
>>> file.replace("\x5c","\x5c\x5c")  #can't work 
'D:\tdx\x0bipdoc\\\\shf10\xc0383.Txt'

我想把它分成两部分,但失败了。

>>> filename = 'D:\tdx\vipdoc\szf10\300383.Txt'
>>> re.search('(.*?)(\d+\.Txt)',filename).group(1)
'D:\tdx\x0bipdoc\\szf10\xc0'
>>> re.search('(.*?)(\d+\.Txt)',filename).group(2)
'383.Txt'

在 Martijn Pieters 的帮助下,我通过在映射中添加 '\300':r'\300' 解决了这个问题。

mapping = {'\a': r'\a', '\b': r'\b', '\f': r'\f', '\n': r'\n',
       '\r': r'\r', '\t': r'\t', '\v': r'\v','\300':r'\300'}
filename = 'D:\tdx\vipdoc\szf10\300383.Txt'
for char, escaped in mapping.items():
    filename = filename.replace(char, escaped)

5 个回答

0

你不需要做什么复杂的事情,Python里有内置的工具可以处理这种问题,特别是 os.path.normpath 这个功能。想了解具体的实现细节,可以看看 这篇文章

0

我觉得,如果你最开始是用没有转义的版本来写的,文件名里可能会有一些特殊字符。而且这些文件也会在脚本最开始运行的那个目录里。

\t会变成一个制表符,\v是一个垂直制表符,\s没问题,\300则是一个高位的ASCII字符。

我建议你在Python里运行以下命令:

import shutil
shutil.move('D:\tdx\vipdoc\szf10\300383.Txt',r'D:\tdx\vipdoc\szf10\300383.Txt')

确保你在脚本最开始运行的同一个目录下执行这个命令,这样文件就会放在你预期的位置,文件名也会是你想要的。

从那以后,你就可以使用正确的版本了。

0

你通常不能这样做,因为比如说 'D:\tdx' 这个路径中的 \t 会被当作一个制表符来处理。不过,你可以尝试把那些转义字符转换成看起来像原始字符串的东西,但这比一开始就正确地写这个文件名要麻烦得多。

2

如果你使用 '' 而不是 r'',那么在你的字符串里面每个反斜杠都需要手动加上转义符:

filename = 'D:\\tdx\\vipdoc\\szf10\\300383.Txt'

使用 r'' 更简单,因为它会让反斜杠 \ 不被当作转义字符来处理,这样当你只是想要一个普通的反斜杠的时候,就不需要再加转义符了。

7

因为你那个“坏掉”的文件名其实并不包含 \ 字符,所以你也无法替换这些字符。你有一个 ASCII 9 的制表符(TAB),而不是两个分开的字符 \t

>>> len('\t')
1
>>> '\' in '\t'
False

你需要尝试“修复”这个坏掉的字符串;这并不是万无一失的,但你可以创建一个替换表来处理一些常见的转义序列。对于文件名来说,通常不处理回车、制表符或换页符,这样做是完全可行的。

Python 的字符串字面量只支持有限数量的一字母 \ 转义序列;你可以查看 Python 字符串字面量文档

\a  ASCII Bell (BEL)     
\b  ASCII Backspace (BS)     
\f  ASCII Formfeed (FF)  
\n  ASCII Linefeed (LF)  
\r  ASCII Carriage Return (CR)   
\t  ASCII Horizontal Tab (TAB)   
\v  ASCII Vertical Tab (VT)

我省略了多字符的序列,因为这些在定义字面量时容易出错。只需将这些字符替换为转义序列即可:

mapping = {'\a': r'\a', '\b': r'\b', '\f': r'\f', '\n': r'\n',
           '\r': r'\r', '\t': r'\t', '\v': r'\v'}

for char, escaped in mapping.items():
    filename = filename.replace(char, escaped)

另外,我们可以用 'string_escape' 编解码器来映射这些字符:

>>> '\t'.encode('string_escape')
'\\t'

不过,你不能把这个应用到整个字符串上,因为那样会让任何正确转义的反斜杠重复出现。此外,对于上面提到的许多转义码,它会使用 \xhh 转义序列 而不是

>>> '\a'.encode('string_escape')
'\\x07'

所以这个方法并不太适合你的需求。

对于用 \xhh 编码的字符,修复起来要困难得多。例如,Windows 文件系统很好地支持 Unicode 码点。如果你假设只使用 ASCII 码点,那么就会简单一些。你可以使用正则表达式将这些字符替换为它们的“转义”版本:

import re

filename = re.sub(r'[\x80-\xff]', lambda m: m.group().encode('string_escape'), filename)

这会将任何超出 ASCII 范围的字节转换为转义序列:

>>> import re
>>> re.sub(r'[\x80-\xff]', lambda m: m.group().encode('string_escape'), '\xc0')
'\\xc0'

通过仔细选择字符范围,上面的做法也可以应用于所有不可打印的 ASCII 字符,并用 一个 表达式修复大部分这样的坏文件名,前提是我们首先应用上面的映射来替换那些 'string_escape' 处理不当的代码:

def repair_filename(filename):
    mapping = {'\a': r'\a', '\b': r'\b', '\f': r'\f', '\v': r'\v'}
    for char, escaped in mapping.items():
        filename = filename.replace(char, escaped)
    filename = re.sub(r'[\x00-\x1f\x7f-\xff]', 
                      lambda m: m.group().encode('string_escape'),
                      filename)
    return filename

在你的示例输入上演示:

>>> def repair_filename(filename):
...     mapping = {'\a': r'\a', '\b': r'\b', '\f': r'\f', '\v': r'\v'}
...     for char, escaped in mapping.items():
...         filename = filename.replace(char, escaped)
...     filename = re.sub(r'[\x00-\x1f\x7f-\xff]', 
...                       lambda m: m.group().encode('string_escape'),
...                       filename)
...     return filename
... 
>>> filename = 'D:\tdx\vipdoc\szf10\300383.Txt'
>>> repair_filename(filename)
'D:\\tdx\\vipdoc\\szf10\\xc0383.Txt'

这应该能修复 大部分 这样的坏文件名。比如,它不会修复 \x09,因为那也会被替换成 \\t

它也无法检测 八进制 转义码,也不能修复它们。请注意,\300 被修复成了 \xc0。这需要反复尝试,测试所有可能的组合,或者对输入做出假设。你可以假设 \xhh 从不出现,但 \ooo 会出现,例如。

在这种情况下,表达式变成:

filename = re.sub(r'[\x00-\x1f\x7f-\xff]', lambda m: '\\{:o}'.format(ord(m.group())), filename)

演示:

>>> def repair_filename(filename):
...     mapping = {'\a': r'\a', '\b': r'\b', '\f': r'\f', '\v': r'\v'}
...     for char, escaped in mapping.items():
...         filename = filename.replace(char, escaped)
...     filename = re.sub(r'[\x00-\x1f\x7f-\xff]', 
...                       lambda m: '\\{:o}'.format(ord(m.group())),
...                       filename)
...     return filename
... 
>>> repair_filename(filename)
'D:\\11dx\\vipdoc\\szf10\\300383.Txt'

什么有效,什么无效在很大程度上取决于你期望的文件名类型。如果你知道文件名的最后部分总是以六位数字结尾,那么可以做更多的事情。

不过,最好的办法当然是完全避免文件名损坏。

撰写回答