Jak usunąć wszystkie wiersze w pliku, które mają mniej niż 6 znaków?

17

Mam plik zawierający około 10 milionów linii.

Chcę usunąć wszystkie wiersze w pliku, które mają mniej niż sześć znaków.

Jak mam to zrobic?

Powiedz mi dlaczego
źródło
Czy to pytanie nie jest bardziej odpowiednie dla Stackoverflow?
user1073075
2
@ user1073075 jest tutaj doskonale na temat.
Seth

Odpowiedzi:

30

Istnieje wiele sposobów, aby to zrobić.

Używanie grep:

grep -E '^.{6,}$' file.txt >out.txt

Teraz out.txtbędzie zawierać wiersze mające sześć lub więcej znaków.

Odwrotna droga:

grep -vE '^.{,5}$' file.txt >out.txt

Używanie sed, usuwanie linii o długości 5 lub mniejszej:

sed -r '/^.{,5}$/d' file.txt

Odwrotnie, drukowanie linii o długości sześciu lub większej:

sed -nr '/^.{6,}$/p' file.txt 

Możesz zapisać dane wyjściowe w innym pliku za pomocą >operatora podobnego greplub edytować plik w miejscu za pomocą -iopcji sed:

sed -ri.bak '/^.{6,}$/' file.txt 

Kopia zapasowa oryginalnego pliku zostanie utworzona, file.txt.baka zmodyfikowany plik będzie file.txt.

Jeśli nie chcesz przechowywać kopii zapasowej:

sed -ri '/^.{6,}$/' file.txt

Używając powłoki, Wolniej, nie rób tego , to tylko w celu pokazania innej metody:

while IFS= read -r line; do [ "${#line}" -ge 6 ] && echo "$line"; done <file.txt

Korzystanie pythonnawet wolniej niż grep, sed:

#!/usr/bin/env python2
with open('file.txt') as f:
    for line in f:
        if len(line.rstrip('\n')) >= 6:
            print line.rstrip('\n')

Lepsze wykorzystanie rozumienia listy, aby być bardziej Pythonicznym:

#!/usr/bin/env python2
with open('file.txt') as f:
     strip = str.rstrip
     print '\n'.join([line for line in f if len(strip(line, '\n')) >= 6]).rstrip('\n')
heemayl
źródło
Tak! Miałem nadzieję na odpowiedź pytona =)
TellMeWhy
@DevRobot Widzę .. następnie sprawdź listę ze zrozumieniem, którą dodałem, bądź bardziej Pythonic ..
heemayl
1
Również @DevRobot nie jest pewien, czy Python działa wolniej na dużych plikach, gdy używana jest pierwsza opcja. Właściwie jestem całkiem pewien, że Python działa szybciej na milionach linii, ponieważ czyta w jednej linii.
Jacob Vlijm
1
Drugi przykład python czyta cały plik do pamięci przed wykonaniem łączenia. Myślę, że pierwszy przykład Pythona jest lepszy w tym przypadku.
Holloway
Czytanie według wierszy jest z konieczności wolniejsze, ponieważ pliki nie mają takiej struktury. W każdym razie musisz przeczytać blok z przodu i poszukać nowej linii ze zmniejszonymi możliwościami równoległości, a następnie zwrócić tylko ciąg częściowy. Potrzebujesz bufora okrągłego. Musisz dynamicznie przydzielić pamięć, jeśli nie wiesz, jak długie mogą być linie.
The Vee
19

To jest bardzo proste:

grep ...... inputfile > resultfile   #There are 6 dots

Jest to niezwykle wydajne, ponieważ grepnie będzie parsowało więcej niż potrzebuje, ani nie interpretuje znaków w żaden sposób: po prostu wysyła (całą) linię do standardowego wyjścia (które powłoka przekierowuje do pliku wynikowego), gdy tylko zobaczy 6 znaki w tej linii ( .w kontekście wyrażenia regularnego dopasowuje dowolny 1 znak).

Zatem grep wypisuje tylko wiersze zawierające 6 (lub więcej) znaków, a pozostałe nie są wyprowadzane przez grep, więc nie robią tego z plikiem wynikowym.

Olivier Dulac
źródło
14

Rozwiązanie nr 1: Używając C.

Najszybszy sposób: skompiluj i uruchom ten program C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAX_BUFFER_SIZE 1000000

int main(int argc, char *argv[]) {
    int length;

    if(argc == 3)
        length = atoi(argv[2]);
    else
        return 1;

    FILE *file = fopen(argv[1], "r");

    if(file != NULL) {
        char line[MAX_BUFFER_SIZE];

        while(fgets(line, sizeof line, file) != NULL) {
            char *pos;

            if((pos = strchr(line, '\n')) != NULL)
                *pos = '\0';
            if(strlen(line) >= length)
                printf("%s\n", line);
        }

        fclose(file);
    }
    else {
        perror(argv[1]);
        return 1;
    }

    return 0;
}

Skompiluj z gcc program.c -o program, uruchom z ./program file line_length(gdzie file= ścieżka do pliku i line_length= minimalna długość linii, w twoim przypadku 6; maksymalna długość linii jest ograniczona do 1000000znaków na linię; możesz to zmienić, zmieniając wartość MAX_BUFFER_SIZE).

(Trick zastąpił \nze \0znalezionych tutaj ).

Porównanie ze wszystkimi innymi rozwiązaniami zaproponowanymi w tym pytaniu, z wyjątkiem rozwiązania powłoki (uruchomienie testowe na pliku ~ 91 MB z liniami 10M o średniej długości 8 znaków):

time ./foo file 6

real    0m1.592s
user    0m0.712s
sys 0m0.160s

time grep ...... file

real    0m1.945s
user    0m0.912s
sys 0m0.176s

time grep -E '^.{6,}$'

real    0m2.178s
user    0m1.124s
sys 0m0.152s

time awk 'length>=6' file

real    0m2.261s
user    0m1.228s
sys 0m0.160s

time perl -lne 'length>=6&&print' file

real    0m4.252s
user    0m3.220s
sys 0m0.164s

sed -r '/^.{,5}$/d' file >out

real    0m7.947s
user    0m7.064s
sys 0m0.120s

./script.py >out
real    0m8.154s
user    0m7.184s
sys 0m0.164s

Rozwiązanie nr 2: Korzystanie z AWK:

awk 'length>=6' file
  • length>=6: jeśli length>=6zwraca PRAWDA, drukuje bieżący rekord.

Rozwiązanie nr 3: Używając Perla:

perl -lne 'length>=6&&print' file
  • Jeśli lenght>=6zwróci wartość PRAWDA, drukuje bieżący rekord.

% cat file
a
bb
ccc
dddd
eeeee
ffffff
ggggggg
% ./foo file 6
ffffff
ggggggg
% awk 'length>=6' file   
ffffff
ggggggg
% perl -lne 'length>=6&&print' file
ffffff
ggggggg
kos
źródło
1
Uwierzcie mi ... Czekałem na wasze awk rozwiązanie ..
heemayl
2
@ heemayl I nie zobaczyłem pytania od razu, więc wiedziałem, że gdybyś był online, byłbyś szybszy. Musiałem usunąć moje sedrozwiązanie (zdarza się, wiem). XD
kos
Jaki jest sens poszmiennej? Rozumiem, że zwraca wskaźnik do znaku lineze znakiem nowej linii, ale wydaje się, że nigdy go nie używasz. A jeśli go nie znajdziesz, po prostu ustaw go na równy \0.
user1717828,
@ user1717828 Gdybym znaleźć to mogę wymienić go \0( strchr()zwraca wskaźnik NULL jeżeli znak nie został znaleziony). Chodzi o to, aby zastąpić każdą nową linię na końcu każdej linii, \0tak aby nowa linia nigdy nie była liczona przez strlen(): jest to tak, że długość zawsze można porównać do 6, niezależnie od potencjalnie brakującej nowej linii w ostatniej linii. Wiem, że inne traktowanie tylko ostatniej linii byłoby znacznie bardziej wydajne. Prawdopodobnie zaktualizuję to później.
Kos
1
@tripleee Pomysł polegał na dodaniu rozwiązania przydatnego w przypadku czegoś więcej niż jednorazowej pracy lub nawet większych plików, ale : przetestowałem greprozwiązanie dla tego samego pliku i jest ono rzeczywiście szybsze (prawdopodobnie dlatego, że strlen()nie jest to najlepszy pomysł tutaj) . Spróbuję użyć getchar()pętli, aby zamiast tego sprawdzić tylko pierwszą literę N, myślę, że powinna to wyraźnie poprawić. I tak, każda linia powyżej długości bufora jest po prostu przycięta do długości bufora.
Kos
2

Możesz używać Vima w trybie Ex:

ex -sc 'v/\v.{6}/d' -cx file
  1. \v włącz magię

  2. .{6} znajdź wiersze z co najmniej 6 znakami

  3. v Odwróć wybór

  4. d usunąć

  5. x Zapisz i zamknij

Steven Penny
źródło
1

Rozwiązanie Ruby:

$ cat input.txt                                                                                                          
abcdef
abc
abcdefghijk

$ ruby -ne 'puts $_ if $_.chomp.length() >= 6 ' < input.txt                                                              
abcdef
abcdefghijk

Prosty pomysł: przekieruj plik do standardowego ruby ​​i wypisz linię ze standardowego, tylko jeśli jego długość jest większa lub równa 6

Sergiy Kolodyazhnyy
źródło