HOWTO · Python

在 Python 中從檔案中刪除特定行

本教程討論如何在 Python 中從檔案中刪除特定行。

本頁內容

Python 沒有內建操作可直接從文字檔中移除任意一行。先讀取各行,略過要移除的行,再將其餘內容寫回檔案。以下範例使用臨時檔案,皆可獨立執行。

依行號刪除 Python 檔案中的行

Python 的 enumerate() 會為每一行提供從零開始的索引:第一行的索引是 0。請先讀取檔案,再以寫入模式開啟,因為 w 模式會立即清空檔案內容。若要選取多個索引,請使用集合;索引在集合中的行都會被略過。

執行以下範例:

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("red\ngreen\nblue\nyellow\n")
    indexes_to_remove = {1, 3}
    lines = path.read_text().splitlines(keepends=True)
    path.write_text("".join(
        line for index, line in enumerate(lines)
        if index not in indexes_to_remove
    ))
    print(path.read_text(), end="")
red
blue

此範例只使用 LF 換行,並以 LF 換行寫入保留的行。Path.read_text() 會使用通用換行處理,因此 CRLF 輸入在寫入前會轉換成 \n。若索引超出檔案範圍,就不會匹配任何行,也不會造成影響。處理大型檔案時,可以改用另一種方式:逐行處理並寫入臨時檔案,等兩個檔案都關閉後,再取代原始檔案。

方法一

即使程式提早離開區塊,內容管理器仍會關閉檔案。先讀取再寫回的方式簡單明瞭,適用於任意選取的索引,但會將所有行保留在記憶體中。處理大型檔案時,請在同一個目錄建立臨時檔案,並在成功寫入後取代原始檔案。

方法二

若檔案大小適合載入記憶體,r+ 可讓你透過同一個檔案控制代碼讀取和寫入。此處的 newline="" 會停用換行轉換:readlines() 會保留每行的結尾字元,包括 CRLF,而 writelines() 會原樣寫入保留下來的結尾字元。讀取完成後,先將檔案位置移回開頭,再截斷舊內容,然後寫入保留的行。這樣不必重複開啟原始檔案,但這不是原子更新:若重寫途中發生錯誤,檔案可能只剩部分內容。這種方式本身不會比第一種方式快。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("red\ngreen\nblue\nyellow\n")
    indexes_to_remove = {1, 3}
    with path.open("r+", newline="") as file:
        lines = file.readlines()
        file.seek(0)
        file.truncate()
        file.writelines(
            line for index, line in enumerate(lines)
            if index not in indexes_to_remove
        )
    print(path.read_text(), end="")
red
blue

從 Python 檔案中刪除第一行或最後一行

使用串列切片可以移除指定位置的行,同時保留其餘內容。lines[1:] 會略過第一行;lines[:-1] 會略過最後一行。寫入切片結果前,先確認串列中有行,這樣同一段程式也能處理空檔案。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("first\nmiddle\nlast\n")
    lines = path.read_text().splitlines(keepends=True)
    without_first = lines[1:]
    path.write_text("".join(without_first))
    lines = path.read_text().splitlines(keepends=True)
    without_last = lines[:-1] if lines else []
    path.write_text("".join(without_last))
    print(path.read_text(), end="")
middle

範例會先移除第一行,再從剩餘內容中移除最後一行。若只要執行其中一項操作,保留相應的切片並寫入一次即可。對空串列進行切片仍會得到空串列。

刪除內容完全符合指定文字的行

比較每行內容與目標字串時,請先去除行尾字元。這是區分大小寫的完全比對:空格和其他字元都會影響結果。以下迴圈會移除所有符合條件的行。此範例只使用 LF 換行;若使用 Path.read_text(),CRLF 輸入會在寫入前正規化為 \n。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("keep this\nremove this\nremove this\nkeep that\n")
    target = "remove this"
    lines = path.read_text().splitlines(keepends=True)
    retained = [
        line for line in lines
        if line.rstrip("\r\n") != target
    ]
    path.write_text("".join(retained))
    print(path.read_text(), end="")
keep this
keep that

若比較的文字可能包含開頭或結尾空格,請使用 line.rstrip("\r\n"),不要使用 strip()。若只想移除第一個符合條件的行,請先找出它的索引,再只略過該位置。

刪除包含指定單字的行

若要移除包含某個單字或子字串的行,請使用 in 測試,而不是比對整行。此範例會移除所有包含 blue 的行;比對會區分大小寫,而且包含 bluebird 的行也會符合條件。先將篩選後的文字寫入臨時檔案,可讓原始檔案在結果準備完成前保持不變。

import os
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    temporary_path = Path(directory) / "filtered.txt"
    path.write_text("red\nblue bird\ngreen\nbluebird\n")
    with path.open("r") as source, temporary_path.open("w") as target:
        for line in source:
            if "blue" not in line:
                target.write(line)
    os.replace(temporary_path, path)
    print(path.read_text(), end="")
red
green

請在與原始檔案相同的目錄中建立臨時檔案,確保取代作業在同一個檔案系統上進行。呼叫 os.replace() 前,請先關閉兩個檔案控制代碼。若子字串比對範圍太廣,可改用較精確的條件,例如透過正規表示式檢查完整單字。

刪除 Python 檔案中最短的一行

只去除行尾字元後再比較各行長度。使用 min() 可選出索引,不必猜測一個最大行長度。若多行的長度同為最短,會選取第一行;依索引移除可保留後面內容相同的行。此範例只使用 LF 換行,並以 LF 換行寫入保留的行;Path.read_text() 會在寫入前將 CRLF 輸入正規化為 \n。若檔案沒有任何行,就沒有內容需要移除。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("long line\nx\ncat\ny\n")
    lines = path.read_text().splitlines(keepends=True)
    if lines:
        shortest_index = min(
            range(len(lines)),
            key=lambda index: len(lines[index].rstrip("\r\n")),
        )
        del lines[shortest_index]
    path.write_text("".join(lines))
    print(path.read_text(), end="")
long line
cat
y

此處的 x 和 y 同為最短行,因此只會移除較早出現的 x。若空白行不應納入比較,請在選取索引時先排除空白行,並在輸出中保留它們。

刪除 Python 檔案中的所有行

以寫入模式開啟現有檔案時,檔案會立即被截斷。只有在確定要捨棄所有內容時才使用此方式。截斷檔案需要可寫入的檔案控制代碼;以唯讀模式開啟的檔案不能截斷。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("first\nsecond\n")
    with path.open("w"):
        pass
    print(repr(path.read_text()))
''