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?
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?
Odpowiedzi:
Istnieje wiele sposobów, aby to zrobić.
Używanie grep
:
grep -E '^.{6,}$' file.txt >out.txt
Teraz out.txt
bę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 grep
lub edytować plik w miejscu za pomocą -i
opcji sed
:
sed -ri.bak '/^.{6,}$/' file.txt
Kopia zapasowa oryginalnego pliku zostanie utworzona, file.txt.bak
a 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 python
nawet 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')
To jest bardzo proste:
grep ...... inputfile > resultfile #There are 6 dots
Jest to niezwykle wydajne, ponieważ grep
nie 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.
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 1000000
znaków na linię; możesz to zmienić, zmieniając wartość MAX_BUFFER_SIZE
).
(Trick zastąpił \n
ze \0
znalezionych 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
awk 'length>=6' file
length>=6
: jeśli length>=6
zwraca PRAWDA, drukuje bieżący rekord.perl -lne 'length>=6&&print' file
lenght>=6
zwró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
awk
rozwiązanie ..
sed
rozwiązanie (zdarza się, wiem). XD
pos
zmiennej? Rozumiem, że zwraca wskaźnik do znaku line
ze 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
.
\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, \0
tak 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.
grep
rozwią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.
Możesz używać Vima w trybie Ex:
ex -sc 'v/\v.{6}/d' -cx file
\v
włącz magię
.{6}
znajdź wiersze z co najmniej 6 znakami
v
Odwróć wybór
d
usunąć
x
Zapisz i zamknij
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