używanie Pythona do usuwania określonej linii w pliku

145

Powiedzmy, że mam plik tekstowy pełen pseudonimów. Jak mogę usunąć określony pseudonim z tego pliku, używając Pythona?

SourD
źródło
1
Spróbuj, fileinputjak opisał @ jf-sebastian tutaj . Wydaje się, że pozwala na pracę wiersz po wierszu, za pośrednictwem pliku tymczasowego, wszystko z prostą forskładnią.
Kevin

Odpowiedzi:

205

Najpierw otwórz plik i pobierz wszystkie swoje wiersze z pliku. Następnie ponownie otwórz plik w trybie zapisu i zapisz swoje wiersze z powrotem, z wyjątkiem wiersza, który chcesz usunąć:

with open("yourfile.txt", "r") as f:
    lines = f.readlines()
with open("yourfile.txt", "w") as f:
    for line in lines:
        if line.strip("\n") != "nickname_to_delete":
            f.write(line)

Potrzebujesz strip("\n")znaku nowej linii w porównaniu, ponieważ jeśli twój plik nie kończy się znakiem nowej linii, ostatni też linenie.

houbysoft
źródło
2
dlaczego musimy dwukrotnie otwierać i zamykać?
Ooker
3
@Ooker: Musisz dwukrotnie otworzyć plik (i zamknąć go w międzyczasie), ponieważ w pierwszym trybie jest on „tylko do odczytu”, ponieważ właśnie czytasz bieżące wiersze pliku. Następnie zamykasz go i ponownie otwierasz w „trybie zapisu”, w którym plik jest zapisywalny i zastępujesz zawartość pliku bez linii, którą chciałeś usunąć.
Devin
4
Dlaczego Python nie pozwala nam tego zrobić w jednej linii?
Ooker
5
@Ooker, Kiedy czytasz wiersz, spróbuj wyobrazić sobie kursor poruszający się wzdłuż wiersza podczas czytania. Po przeczytaniu tej linii kursor znajduje się za nią. Kiedy próbujesz pisać do pliku, piszesz, gdzie aktualnie znajduje się kursor. Ponowne otwarcie pliku powoduje zresetowanie kursora.
Waddas
4
Użyj ze związkiem!
Sceluswe,
100

Rozwiązanie tego problemu przy tylko jednym otwarciu:

with open("target.txt", "r+") as f:
    d = f.readlines()
    f.seek(0)
    for i in d:
        if i != "line you want to remove...":
            f.write(i)
    f.truncate()

To rozwiązanie otwiera plik w trybie r / w ("r +") i używa funkcji seek do zresetowania wskaźnika f, a następnie obcięcia, aby usunąć wszystko po ostatnim zapisie.

Lother
źródło
2
Działało to bardzo dobrze dla mnie, ponieważ musiałem również użyć pliku blokującego (fcntl). Nie mogłem znaleźć żadnego sposobu na użycie fileinput razem z fcntl.
Easyrider
1
Fajnie byłoby zobaczyć efekty uboczne tego rozwiązania.
user1767754
3
Nie zrobiłbym tego. Jeśli pojawi się błąd w forpętli, otrzymasz częściowo nadpisany plik z zduplikowanymi wierszami lub obciętą do połowy linią. Zamiast tego możesz chcieć f.truncate()zaraz potem f.seek(0). W ten sposób, jeśli pojawi się błąd, skończysz z niekompletnym plikiem. Ale prawdziwym rozwiązaniem (jeśli masz miejsce na dysku) jest wyjście do pliku tymczasowego, a następnie użycie os.replace()lub pathlib.Path(temp_filename).replace(original_filename)zamiana go z oryginałem, gdy wszystko się powiedzie.
Boris
Możesz dodać, i.strip('\n') != "line you want to remove..."jak wspomniano w zaakceptowanej odpowiedzi, że to doskonale rozwiązałoby mój problem. Ponieważ po prostu inic dla mnie nie zrobiłem
Mangohero 1
31

Najlepszą i najszybszą opcją, zamiast przechowywać wszystko na liście i ponownie otwierać plik, aby go zapisać, jest moim zdaniem ponowne zapisanie pliku w innym miejscu.

with open("yourfile.txt", "r") as input:
    with open("newfile.txt", "w") as output: 
        for line in input:
            if line.strip("\n") != "nickname_to_delete":
                output.write(line)

Otóż ​​to! W jednej pętli i tylko jednej możesz zrobić to samo. Będzie dużo szybciej.

Barnabe
źródło
Zamiast używać zwykłej pętli for możemy skorzystać z wyrażenia generatora. W ten sposób program nie załaduje wszystkich linii z pliku do pamięci, co nie jest dobrym pomysłem w przypadku dużych plików. W danym momencie będzie mieć w pamięci tylko jedną linię. Z generatorem wyrażenie pętli będzie wyglądać następująco,(output.write(line) for line in input if line!="nickname_to_delete"+"\n")
shrishinde
4
@ShriShinde Nie wczytujesz pliku do pamięci podczas wykonywania pętli po obiekcie pliku, więc to rozwiązanie działa identycznie jak Twoja sugestia.
Steinar Lima
Możesz chcieć usunąć oryginalny plik i zmienić nazwę drugiego pliku na nazwę oryginalnego pliku, która w Pythonie w systemie Linux wyglądałaby następująco,subprocess.call(['mv', 'newfile.txt', 'yourfile.txt'])
maks.
6
os.replace(nowość w Pythonie w wersji 3.3) jest bardziej wieloplatformowa niż wywołanie systemowe mv.
7yl4r
Proste i świetne.
JuBaer AD
27

To jest „widelec” od @Lother (którą moim zdaniem należy uznać za właściwą).


W przypadku takiego pliku:

$ cat file.txt 
1: october rust
2: november rain
3: december snow

Ten widelec z rozwiązania Lother's działa dobrze:

#!/usr/bin/python3.4

with open("file.txt","r+") as f:
    new_f = f.readlines()
    f.seek(0)
    for line in new_f:
        if "snow" not in line:
            f.write(line)
    f.truncate()

Ulepszenia:

  • with open, które odrzucają użycie f.close()
  • jaśniejsze if/elsedo oceny, czy w bieżącej linii nie ma łańcucha
ivanleoncz
źródło
Jeśli wymagana jest f.seek (0)?
yifan
@yifan yes. W przeciwnym razie zamiast nadpisywać plik, dołączysz go do samego siebie (bez wykluczonych linii).
Boris
5

Problem z czytaniem wierszy w pierwszym przebiegu i wprowadzaniem zmian (usuwanie określonych wierszy) w drugim przebiegu polega na tym, że jeśli rozmiary plików są ogromne, zabraknie pamięci RAM. Zamiast tego lepszym podejściem jest czytanie wierszy jeden po drugim i zapisywanie ich w osobnym pliku, eliminując te, których nie potrzebujesz. Uruchomiłem to podejście z plikami o wielkości do 12-50 GB, a użycie pamięci RAM pozostaje prawie stałe. Tylko cykle procesora pokazują przetwarzanie w toku.

Kingz
źródło
2

Podobało mi się podejście do wpisywania plików, jak wyjaśniono w tej odpowiedzi: Usuwanie linii z pliku tekstowego (python)

Załóżmy na przykład, że mam plik, który ma puste wiersze i chcę usunąć puste wiersze, oto jak to rozwiązałem:

import fileinput
import sys
for line_number, line in enumerate(fileinput.input('file1.txt', inplace=1)):
    if len(line) > 1:
            sys.stdout.write(line)

Uwaga: puste wiersze w moim przypadku miały długość 1

Głęboki
źródło
2

Jeśli używasz Linuksa, możesz wypróbować następujące podejście.
Załóżmy, że masz plik tekstowy o nazwie animal.txt:

$ cat animal.txt  
dog
pig
cat 
monkey         
elephant  

Usuń pierwszą linię:

>>> import subprocess
>>> subprocess.call(['sed','-i','/.*dog.*/d','animal.txt']) 

następnie

$ cat animal.txt
pig
cat
monkey
elephant
Ren
źródło
7
To rozwiązanie nie jest agnostyczne dla systemu operacyjnego, a ponieważ OP nie określił systemu operacyjnego, nie ma powodu, aby publikować odpowiedź imo dla systemu Linux.
Steinar Lima
2
Każdy, kto zasugeruje użycie podprocesu do wszystkiego, co można zrobić za pomocą samego Pythona, otrzyma głos przeciw! I +1 do @SteinarLima ... Zgadzam się
Jamie Lindsey,
2

Myślę, że jeśli wczytasz plik do listy, zrób to, co możesz iterować po liście, aby znaleźć pseudonim, którego chcesz się pozbyć. Możesz to zrobić znacznie wydajnie bez tworzenia dodatkowych plików, ale będziesz musiał zapisać wynik z powrotem do pliku źródłowego.

Oto jak mogę to zrobić:

import, os, csv # and other imports you need
nicknames_to_delete = ['Nick', 'Stephen', 'Mark']

Zakładam, że nicknames.csvzawiera dane takie jak:

Nick
Maria
James
Chris
Mario
Stephen
Isabella
Ahmed
Julia
Mark
...

Następnie załaduj plik na listę:

 nicknames = None
 with open("nicknames.csv") as sourceFile:
     nicknames = sourceFile.read().splitlines()

Następnie przejdź do listy, aby dopasować dane wejściowe do usunięcia:

for nick in nicknames_to_delete:
     try:
         if nick in nicknames:
             nicknames.pop(nicknames.index(nick))
         else:
             print(nick + " is not found in the file")
     except ValueError:
         pass

Na koniec zapisz wynik z powrotem do pliku:

with open("nicknames.csv", "a") as nicknamesFile:
    nicknamesFile.seek(0)
    nicknamesFile.truncate()
    nicknamesWriter = csv.writer(nicknamesFile)
    for name in nicknames:
        nicknamesWriter.writeRow([str(name)])
nicknamesFile.close()
Malik
źródło
1

Ogólnie nie możesz; musisz ponownie napisać cały plik (przynajmniej od momentu zmiany do końca).

W niektórych przypadkach możesz zrobić lepiej niż to -

jeśli wszystkie elementy danych mają taką samą długość i nie są ułożone w określonej kolejności, a znasz przesunięcie elementu, którego chcesz się pozbyć, możesz skopiować ostatni element do elementu, który ma zostać usunięty i skrócić plik przed ostatnim elementem ;

lub możesz po prostu nadpisać fragment danych wartością „to są złe dane, pomiń” lub zachować flagę „ten element został usunięty” w zapisanych elementach danych, tak aby można było oznaczyć go jako usuniętego bez konieczności modyfikowania pliku w inny sposób.

To prawdopodobnie przesada w przypadku krótkich dokumentów (mniej niż 100 KB?).

Hugh Bothwell
źródło
1

Prawdopodobnie masz już poprawną odpowiedź, ale oto moja. Zamiast używać listy do zbierania niefiltrowanych danych (jaka readlines()metoda to robi), używam dwóch plików. Jedna służy do przechowywania głównych danych, a druga do filtrowania danych podczas usuwania określonego ciągu. Oto kod:

main_file = open('data_base.txt').read()    # your main dataBase file
filter_file = open('filter_base.txt', 'w')
filter_file.write(main_file)
filter_file.close()
main_file = open('data_base.txt', 'w')
for line in open('filter_base'):
    if 'your data to delete' not in line:    # remove a specific string
        main_file.write(line)                # put all strings back to your db except deleted
    else: pass
main_file.close()

Mam nadzieję, że okaże się to przydatne! :)

andrii1986
źródło
0

Zapisz wiersze pliku na liście, a następnie usuń z listy wiersz, który chcesz usunąć i zapisz pozostałe wiersze do nowego pliku

with open("file_name.txt", "r") as f:
    lines = f.readlines() 
    lines.remove("Line you want to delete\n")
    with open("new_file.txt", "w") as new_f:
        for line in lines:        
            new_f.write(line)
Henrique Andrade
źródło
Udzielając odpowiedzi, najlepiej jest wyjaśnić, DLACZEGO twoja odpowiedź jest tą jedyną.
Stephen Rauch,
Jeśli plik nie kończy się znakiem nowej linii, ten kod nie usunie ostatniej linii, nawet jeśli zawiera słowo, które chcesz usunąć.
Boris
0

oto inna metoda usuwania / niektórych linii z pliku:

src_file = zzzz.txt
f = open(src_file, "r")
contents = f.readlines()
f.close()

contents.pop(idx) # remove the line item from list, by line number, starts from 0

f = open(src_file, "w")
contents = "".join(contents)
f.write(contents)
f.close()
ungalcrys
źródło
0

Podoba mi się ta metoda wykorzystująca fileinput i metodę „inplace”:

import fileinput
for line in fileinput.input(fname, inplace =1):
    line = line.strip()
    if not 'UnwantedWord' in line:
        print(line)

Jest trochę mniej rozwlekły niż inne odpowiedzi i wystarczająco szybki

Ru887321
źródło
0

Możesz skorzystać z rebiblioteki

Zakładając, że jesteś w stanie załadować pełny plik txt. Następnie definiujesz listę niechcianych pseudonimów, a następnie zastępujesz je pustym ciągiem znaków „”.

# Delete unwanted characters
import re

# Read, then decode for py2 compat.
path_to_file = 'data/nicknames.txt'
text = open(path_to_file, 'rb').read().decode(encoding='utf-8')

# Define unwanted nicknames and substitute them
unwanted_nickname_list = ['SourDough']
text = re.sub("|".join(unwanted_nickname_list), "", text)
mrk
źródło
-1

Aby usunąć określoną linię pliku według jej numeru linii :

Zastąp zmienne nazwa_pliku i line_to_delete nazwą twojego pliku i numerem linii, którą chcesz usunąć.

filename = 'foo.txt'
line_to_delete = 3
initial_line = 1
file_lines = {}

with open(filename) as f:
    content = f.readlines() 

for line in content:
    file_lines[initial_line] = line.strip()
    initial_line += 1

f = open(filename, "w")
for line_number, line_content in file_lines.items():
    if line_number != line_to_delete:
        f.write('{}\n'.format(line_content))

f.close()
print('Deleted line: {}'.format(line_to_delete))

Przykładowe dane wyjściowe :

Deleted line: 3
Aram Maliachi
źródło
nie ma potrzeby budowania dyktatu, po prostu użyjfor nb, line in enumerate(f.readlines())
Dionys
-3

Weź zawartość pliku, podziel go znakiem nowej linii na krotkę. Następnie uzyskaj dostęp do numeru wiersza swojej krotki, dołącz do swojej krotki wyników i nadpisz do pliku.

Nikhil
źródło
6
(1) masz na myśli tuple(f.read().split('\n'))?? (2) „Uzyskaj dostęp do numeru wiersza swojej krotki” i „dołącz do swojej krotki wyników” brzmią raczej tajemniczo; rzeczywisty kod Pythona może być bardziej zrozumiały.
John Machin,