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?


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')

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.


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

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


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.