【问题标题】:Python, How to empty a specific line in filePython,如何清空文件中的特定行
【发布时间】:2016-11-06 09:31:36
【问题描述】:

有趣的是,以前在 SO 中没有问过这样的问题。

我正在将数据行记录到 Python2 中的文本文件中。我想做的是,通过行数,我想擦除一行,但我不希望它被下一个填充,只是保持空白(因此不必每次都写一个新文件删除一行。)

所以我要问的不是这些之一,

基本概念是更改特定行的内容,在这种情况下更改为空字符串。

有一个问题我并没有真正理解,但可以包含我的问题的答案。如果是这样,请帮我理解是怎么回事。

如果您认为我的问题与此问题重复,请在标记他的问题之前向我解释答案。

我对该主题的研究:

编辑:我什至忘了问这样的事情是否可行,我会很感激你的信息。

【问题讨论】:

  • 将特定行替换为空格字符就足够了吗?否则,您将不得不向后翻译相关行之后的所有字节。
  • @fuglede 我想空白字符就足够了,但是我现在意识到,对于我所追求的行为,需要固定的字节长度。就像在 C 中一样。但我可以检查每行的长度并用足够数量的空格替换它们。不过,我仍然不知道如何在 Python 中实现这一点。
  • 正如 fuglede 所说,您可以用空白字节替换不需要的字节,例如空格字符(ASCII 代码 0x20)。传统上,DEL character(ASCII 代码 0x7f)已用于此目的。
  • 我在下面的答案中添加了一种使用 Python 实现这一目标的方法。
  • 文本文件中的行长度是否相同?如果是这样,您可以使用文件.seek 方法快速跳转到任何所需的行。

标签: python python-2.7 file


【解决方案1】:

这是一个就地修改文本文件的函数,将指定的行替换为相同长度的行。

在这个演示中,我使用# 作为替换字符,以便更容易看到发生了什么。您可以使用简单的空格 (chr(32)) 或 ASCII DEL 字符 (chr(127) == \x7f)。使用 DEL 的一个好处是可以更轻松地快速删除所有这些“已删除”行,因为该字符不会出现在文件的任何“正确”行中。

首先,这里有一个小文本文件来测试这段代码。

qdata

1 one
2 two
3 three
4 four
5 five
6 six
7 seven
8 eight
9 nine

这是代码。请注意,它使用从 1 开始的行号。

def erase_line(fname, line_num):
    ''' In-place replacement of line `line_num` in file `fname` with
        a line of DEL chars of the same length, retaining the newline.
    '''
    DEL = '#'
    with open(fname, 'r+') as f:
        for i in range(line_num - 1):
            f.readline()
        start = f.tell()
        line = f.readline()
        line = DEL * (len(line) - 1) + '\n'
        f.seek(start)
        f.write(line)

erase_line('qdata', 3)

这是qdata的修改版本:

1 one
2 two
#######
4 four
5 five
6 six
7 seven
8 eight
9 nine

因为它必须处理不同长度的行,erase_line 必须读取所有行,直到找到所需的行,但它只重写该行,它不会修改任何其他行,所以它应该相当快。如果您的行是固定长度的,我们可以使用.skip 立即跳转到所需的行。


这是一个函数,它将删除所有完全由 DEL 字符组成的行,并将结果写入一个新文件。

def compact(oldname, newname):
    ''' Copy file `oldname` to `newname`, removing lines that
        consist entirely of the DEL char, apart from the '\n'
    '''
    DEL = '#'
    with open(oldname, 'r') as fin, open(newname, 'w') as fout:
        for line in fin:
            if not line.lstrip(DEL) == '\n':
                fout.write(line)

compact('qdata', 'qdata.new')

qdata.new

1 one
2 two
4 four
5 five
6 six
7 seven
8 eight
9 nine

最后,这是一个执行压缩操作的 Unix/Linux 管道,假设您使用的是实际的 DEL 字符(即八进制的 \177)。它可能比我的 Python 版本更快。

tr -d '\177' <qdata | awk '!/^$/' >qdata.new

【讨论】:

    【解决方案2】:

    你追求的是这样的东西吗?

    def remove_line_from_file(filename, line_number):
        with open(filename) as f:
            lines = f.readlines()
        lines[line_number - 1] = '\n'  # <- or whatever kind of newline is relevant for your system
        with open(filename, 'w') as f:
            f.writelines(lines)
    

    那么,如果test文件的内容是

    line 1
    line 2
    line 3
    

    运行remove_line_from_file('test', 2) 将把test 变成

    line 1
    
    line 3
    

    更新,现在我实际上正确地阅读了这个问题:这个方法修改了文件,用空白字符替换了该行的内容:

    def remove_line_from_file(filename, line_number):
        with open(filename, 'r+') as f:
            count = 0
            bytes_read = 0
            while True:
                bytes_read += 1
                this_byte = f.read(1)
                if not this_byte:
                    break
                if this_byte == '\n':
                    count += 1
                    if count == line_number - 1:
                        start = bytes_read
                    elif count == line_number:
                        f.seek(start)
                        f.write(' ' * (bytes_read - start - 1))
                        break
    

    根据上面 PM 2Ring 的评论,使用 chr(127) 而不是 ' ' 也是有意义的。

    【讨论】:

    • 感谢您的回答。你的方法会奏效,但这正是我想要避免的。读取所有数据并一起写入。
    • 啊,对不起,我错过了你问题的那一部分。确实,file.seek 应该是你所追求的。 stackoverflow.com/questions/1877999/… 按照这些思路做了一些事情(但仅针对最后一行,这意味着它不能立即被采用)。
    • 使用 DEL 字符的好处是您可以快速“压缩”文件,即复制文件,删除所有 DEL,可以使用 Python 脚本,也可以使用标准 *nix @ 987654332@ 实用程序。
    【解决方案3】:

    你说得对,fileinput module 正是你所需要的:

    import fileinput
    def blank_line(filename, lineno):
        f = fileinput.input(files=[filename], inplace=True)
        for line in f:
            if fileinput.lineno() == lineno: # note: line numbers start at 1, not 0
                line = ""
            print line.rstrip("\n") # Output is redirected to the current line of the file
        f.close()
    

    请注意,Python 3 在这里有一些优势:fileinput 支持上下文管理器(with 语句),新的 print() 函数允许我们保持原样保留行(而不是总是添加换行符)或末尾的空格)。

    【讨论】:

    • 但是 OP 说他们不想“每次擦除一行时都写一个新文件”。他们只想修改他们想要删除的单行。
    • 我想节省 CPU 周期而不是内存。当然,随着行数的增加,我会清理文件。
    【解决方案4】:

    您应该了解大多数系统上的文本文件是如何存储在磁盘或其他存储介质上的。

    虽然不同系统之间的细节或多或少有所不同,但今天它们都具有固定大小的“块”的概念。 文件分配在这些块中,文本文件只是一个字符序列,其中一些是0x0A换行代码(*)。

    假设一个块是 32 字节(它们通常比这大,但只是为了使图表更易于阅读)。

     _______text file logical content________
    |Hello, world¶                           |
    |This is a text file that contains¶      |
    |three lines¶____________________________|
    
     _______________________a 32 bytes block______________________
    |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|
    |H|e|l|l|o|,| |w|o|r|l|d|.|¶|T|h|i|s| |i|s| |a| |t|e|x|t| |f|i|
    |l|e| |t|h|a|t| |c|o|n|t|a|i|n|s|¶|t|h|r|e|e| |l|i|n|e|s|¶|_|_|
    

    如您所见,三行占用了两个块,第二个块的最后两个字节未使用。

    文件系统会注意不显示额外的两个字节,但关键是文本文件的“行”无关紧要 与磁盘上文件的结构:所有行都是连续写入的,它们之间有特殊的换行符(**)。

    例如,如果您想将一行替换为具有相同长度的另一行,您可以只更新这几个字节。相反,如果该行的长度不同,或者您想删除或插入新行,唯一的解决方案是从该点实际重写整个文件。

    (*) 题外话:MS-DOS 很久以前和以后使用 Windows 使用 两个 字符 0x0D+0x0A 来标记换行符,因为......好吧......没人知道可以肯定的是:这是一个愚蠢的愚蠢不可原谅的选择,即使是在当时,也没有真正的充分理由,我们都必须永远生活下去。这种使用两个换行符的错误是“二进制模式”疯狂的根源。

    (**) 第二个题外话:即使在今天也有非常“常见”的文件系统,其中文本文件具有固定长度的行而不是使用行终止字符,但它们仅用于存储银行账户、保险单和其他绝对重要的信息不断被 COBOL 程序洗牌,其中的源代码早已丢失,而且无论如何也没有人维护过任何严肃的存储库。如果这让你感到害怕,那么就忽略它们,把你所有的钱都藏在床垫下。

    【讨论】:

    • 也许我需要的是 Python 的链表等价物(但仍存储在硬盘中,在文件中)。因为我要使用的结构正是双端队列。我想知道操作系统中是否存在这种数据类型。
    猜你喜欢
    • 2011-06-22
    • 2020-06-04
    • 2017-09-23
    • 2017-07-16
    • 1970-01-01
    • 2013-11-09
    • 1970-01-01
    • 1970-01-01
    • 2020-03-05
    相关资源
    最近更新 更多