从文本文件中删除每行的第一个字符

4

我是新手,对Python和编程都不太了解。

我想从文本文件的每一行中删除第一个字符,并将更改写回到文件中。例如,我有一个包含36行的文件,每行的第一个字符都包含一个符号或数字,我希望将其删除。

我写了一小段代码,但它并没有按照预期工作,它只是复制整个行。非常感谢您的帮助!

from sys import argv

run, filename = argv

f = open(filename, 'a+')
f.seek(0)
lines = f.readlines()
for line in lines:
    f.write(line[1:])
f.close()

1
你的目标是编写程序还是删除字符?如果是后者,则执行以下命令:sed -i 's/^.//' filename.txt - Robᵩ
只需删除字符。顺便问一下,这是正则表达式吗?我该如何使用您的代码行? - izdi
要使用Rob的解决方案,您需要安装“sed”软件。 - eyquem
@skzd - 假设您正在运行Unix或Linux,您可以从shell提示符中运行该命令。如果您正在运行Windows,则我不知道您可能使用哪个命令。 - Robᵩ
6个回答

6

你的代码已经删除了第一个字符。我将确切地保存了你的代码,分别命名为 dupy.pydupy.txt,然后运行了 python dupy.py dupy.txt,结果如下:

from sys import argv

run, filename = argv

f = open(filename, 'a+')
f.seek(0)
lines = f.readlines()
for line in lines:
    f.write(line[1:])
f.close()
rom sys import argv
un, filename = argv
 = open(filename, 'a+')
.seek(0)
ines = f.readlines()
or line in lines:
   f.write(line[1:])
.close()

它并不是复制整行; 它是复制已去除其第一个字符的行。


但根据您问题的初步陈述,似乎您想要覆盖这些行,而不是追加新副本。为了做到这一点,不要使用append模式。先读取文件,然后再写入文件:

from sys import argv

run, filename = argv

f = open(filename)
lines = f.readlines()
f.close()
f = open(filename, 'w')
for line in lines:
    f.write(line[1:])
f.close()

或者,你也可以写一个新文件,然后在完成时将其移动到原始文件的顶部。
import os
from sys import argv

run, filename = argv

fin = open(filename)
fout = open(filename + '.tmp', 'w')
lines = f.readlines()
for line in lines:
    fout.write(line[1:])
fout.close()
fin.close()
os.rename(filename + '.tmp', filename)

(请注意,此版本不能直接在Windows上运行,但比实际的跨平台版本简单;如果您需要在Windows上运行,请告诉我,我可以解释如何操作。)
通过使用with语句、直接对文件进行循环而不是调用readlines以及使用tempfile,可以使代码更加简洁、健壮和高效。
import tempfile
from sys import argv

run, filename = argv

with open(filename) as fin, tempfile.NamedTemporaryFile(delete=False) as fout:
    for line in fin:
        fout.write(line[1:])
    os.rename(fout.name, filename)

在大多数平台上,这可以保证“原子写入”——当您的脚本完成时,甚至在运行过程中有人拔掉插头,该文件要么被新版本替换,要么不变;它不会以不可恢复的垃圾形式半途覆盖。
同样的方法在Windows上不起作用。没有太多的工作,就无法在Windows上实现这种“写入临时文件并重命名”的算法。但是只需要额外做一点工作,就可以接近实现。
with open(filename) as fin, tempfile.NamedTemporaryFile(delete=False) as fout:
    for line in fin:
        fout.write(line[1:])
    outname = fout.name
os.remove(filename)
os.rename(outname, filename)

这样做确实可以防止文件被覆盖一半,但它会留下一个空洞,你可能已经删除了原始文件,并将新文件留在一个临时位置,你必须去搜索它。你可以通过将文件放在更容易找到的地方来使这个过程变得更加优雅(查看NamedTemporaryFile文档以了解如何实现)。或者将原始文件重命名为一个临时的名称,然后写入原始文件名,最后删除原始文件。还有其他各种可能性。但是要想在其他平台上获得相同的行为非常困难。

你的最后一个解决方案的兼容Windows版本是使用内存文件对象,然后在之后将其写出。 - Marcin
@Marcin:不,这完全不等价;它不能保证原子性,这是编写暂存并重命名惯用法的整个意义所在。实际上,它与构建列表并调用“writelines”或甚至使用第一个版本没有什么区别。 - abarnert
abarnert,非常感谢您的澄清! - izdi
1
@skzd abarnert应该已经向您解释了,您代码中的“f.seek(0)”行有两个无用的原因:**1/**它必须放置在读取文件之后,也就是在“lines = f.readlines()”行之后。- **2/**在模式“a”下,在进行任何写入之前,文件指针总是移动到文件末尾。因此,即使将“f.seek(0)”行正确放置在脚本中,它也是无用的。 - eyquem
@eyquem:第二部分并不是保证真实的。如果Python在系统上使用本地的fopen,并且该系统是POSIX2004、C99或Windows with MSVCRT8或更高版本,则可以保证。这就是为什么2.x文档中说“在某些Unix系统上意味着所有写入都附加到文件的末尾,而不管当前的寻址位置”。但除此之外,观点很好。 - abarnert
@abarnert:这对我非常有效。我使用的代码是:[code] with open('c:\NDCs.txt', 'r') as infile,open('c:\StippedNDCs.txt', 'w') as outfile: for line in infile: outfile.write(line[1:]) - Shaji

4
你可以将所有行读入内存,然后重新创建文件。
from sys import argv

run, filename = argv

with open(filename, 'r') as f:
    data = [i[1:] for i in f
with open(filename, 'w') as f:
    f.writelines(i+'\n' for i in data) # this is for linux. for win use \r\n

或者,您可以创建另一个文件,并逐行将数据从第一个文件移动到第二个文件中。然后,如果您愿意,可以将其重命名。
from sys import argv

run, filename = argv

new_name = filename + '.tmp'
with open(filename, 'r') as f_in, open(new_name, 'w') as f_out:
    for line in f_in:
        f_out.write(line[1:])

os.rename(new_name, filename)

你的最新版本在Windows上无法运行。这对于一个示例来说没关系,但你应该提到它。 - abarnert

3
在最基本的情况下,您的问题是在将文件的全部内容读入数组f之后需要seek回到文件开头。由于您正在缩短文件,因此还需要使用truncate来调整完成后文件的官方长度。此外,打开模式a+(a代表追加)覆盖了seek并强制所有写入都到达文件的末尾。所以你的代码应该像这样:
import sys

def main(argv):
    filename = argv[1]
    with open(filename, 'r+') as f:
        lines = f.readlines()
        f.seek(0)
        for line in lines:
            f.write(line[1:])
        f.truncate()

if __name__ == '__main__': main(sys.argv)

在进行类似这样的操作时,更好的做法是将更改写入一个新文件,完成后将其重命名为旧文件。这样可以使更新“原子化”,并发读取器只会看到旧文件或新文件中的一个,而不是两者混合的一种情况。示例如下:

import os
import sys
import tempfile

def main(argv):
    filename = argv[1]
    with open(filename, 'r') as inf:
        with tempfile.NamedTemporaryFile(dir=".", delete=False) as outf:
            tname = outf.name
            for line in inf:
                outf.write(line[1:])
    os.rename(tname, filename)

if __name__ == '__main__': main(sys.argv)

(注意:在Windows上,通过“重命名”原子性地替换文件是行不通的;您必须先使用os.remove删除旧名称。这不幸意味着存在一个短暂的窗口期(无意间的)其中一个并发读取器会发现该文件不存在。据我所知,没有办法避免这种情况。)

你的最新版本在Windows上无法运行。这对于示例来说还好,特别是如果OP不在Windows上,但你应该提到它。 - abarnert
我会尝试这个,我喜欢这个想法。 - izdi

3
import re

with open(filename,'r+') as f:
    modified = re.sub('^.','',f.read(),flags=re.MULTILINE)
    f.seek(0,0)
    f.write(modified)

在正则表达式模式中:
^ 表示“字符串的开头”
带有标志 re.MULTILINE^ 表示“行的开头”
^. 表示“一行开头的唯一一个字符”
一行的开头是字符串的开头或换行符(换行符为 \n)之后的任何位置。
因此,我们可能担心像 \n\n\n\n\n\n\n 这样的序列中的一些换行符会与正则表达式模式匹配。
但是点号表示除了换行符以外的任何字符,因此所有换行符都不与这个正则表达式模式匹配。
在通过 f.read() 触发的文件读取过程中,文件指针会移动到文件末尾。
f.seek(0,0) 将文件指针移回文件的开头。
f.truncate() 在写入停止的地方放置一个新的 EOF = 文件结尾。这是必要的,因为修改后的文本比原始文本短。
将其与没有这行代码的代码进行比较。

2

说实话,我真的不确定嵌套使用with open()好坏如何,但你可以像这样做。

with open(filename_you_reading_lines_FROM, 'r') as f0:
    with open(filename_you_appending_modified_lines_TO, 'a') as f1:
        for line in f0:
            f1.write(line[1:])

嵌套with open并不一定是“坏”的,但很少有必要这样做,因为您可以将它们都放在同一个with语句中,就像其他答案中发布的许多示例一样。 - abarnert

0

虽然似乎有一些关于最佳实践以及它是否能在Windows上运行的讨论,但由于我是Python的新手,我能够运行第一个可用的示例,并使其在我的Win环境中运行,该环境具有cygwin二进制文件,并从样本文件中删除前3个字符(这些字符是行号):

import os
from sys import argv

run, filename = argv

fin = open(filename)
fout = open(filename + '.tmp', 'w')
lines = fin.readlines()
for line in lines:
    fout.write(line[3:])
fout.close()
fin.close()

我选择不自动覆盖,因为我想要能够仔细检查输出结果。

python c:\bin\remove1st3.py sampleCode.txt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接