Як видалити всі рядки у файлі, що мають менше 6 символів?


17

У мене є файл, що містить приблизно 10 мільйонів рядків.

Я хочу видалити всі рядки з файлу, які містять менше шести символів.

Як це зробити?


Хіба це питання більше не підходить для Stackoverflow?
користувач1073075

2
@ user1073075 тут ідеально на тему.
Сет

Відповіді:


30

Існує багато способів зробити це.

Використання grep:

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

Тепер out.txtбуде містити рядки, що мають шість і більше символів.

Зворотний шлях:

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

Використання sed, видалення ліній довжиною 5 або менше:

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

Зворотний шлях, друк ліній довжиною шість і більше:

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

Ви можете зберегти вихід у іншому файлі за допомогою >оператора, як-от grepабо відредагувати файл на місці, використовуючи -iпараметр sed:

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

Оригінальний файл буде резервно створено, як file.txt.bakі модифікований файл file.txt.

Якщо ви не хочете зберігати резервну копію:

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

Використовуючи оболонку, повільніше, не робіть цього , це лише для того, щоб показати інший метод:

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

Використання pythonнавіть повільніше , ніж 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')

Краще використовувати розуміння списку, щоб бути більш пітонічним:

#!/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')

Так! Я сподівався на відповідь пітона =)
TellMeWhy

@DevRobot Я бачу .. тоді перевіряйте список розуміння я додав, будьте більше Pythonic ..
heemayl

1
Також @DevRobot не дуже впевнений, що python повільніший у величезних файлах, коли використовується перший варіант. Насправді я впевнений, що пітон швидший на мільйони рядків, оскільки він читає на рядок.
Яків Влійм

1
Другий приклад пітона читає весь файл в пам'ять перед тим, як зробити з'єднання. Я думаю, що перший приклад пітона є кращим у цьому випадку.
Холлоуей

Читання рядків обов'язково повільніше, оскільки файли не структуровані так. У будь-якому випадку вам потрібно прочитати блок вперед і шукати новий рядок зі зменшеними можливостями паралелізації, а потім повертати лише частковий рядок. Вам потрібен круговий буфер. Вам потрібно динамічно розподіляти пам’ять, якщо ви не знаєте, як довго можуть бути лінії.
The Vee

19

Це дуже просто:

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

Це надзвичайно ефективно, оскільки grepне намагатиметься проаналізувати більше, ніж потрібно, ані інтерпретувати символи жодним чином: він просто надішляє (цілий) рядок до stdout (який оболонка потім перенаправляє на файл результатів), як тільки побачив 6 символів у цьому рядку ( .у контексті регулярного вирівнювання відповідає будь-якому 1 символу).

Таким чином, grep буде виводити лише рядки, що мають 6 (або більше) знаків, а інші не виводяться grep, щоб вони не перетворювали його на файл результатів.


14

Рішення №1: за допомогою C

Найшвидший спосіб: скласти та запустити цю програму 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;
}

Компілюйте gcc program.c -o program, запустіть із ./program file line_length(де file= шлях до файлу і line_length= мінімальна довжина рядка у вашому випадку 6; максимальна довжина рядка обмежена 1000000символами на рядок; ви можете змінити це, змінивши значення MAX_BUFFER_SIZE).

(Trick для заміни \nз \0знайдений тут .)

Порівняння з усіма іншими рішеннями, запропонованими цим питанням, за винятком оболонки (тестовий запуск у файлі ~ 91 Мб із 10М рядками із середньою довжиною 8 символів):

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

Рішення №2: використання AWK:

awk 'length>=6' file
  • length>=6: якщо length>=6повертає TRUE, друкує поточну запис.

Рішення №3: використання Perl:

perl -lne 'length>=6&&print' file
  • Якщо lenght>=6поверне TRUE, друкує поточну запис.

% 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
Повірте .. Я чекав вашого awk рішення ..
heemayl

2
@heemayl І я не побачив це питання одразу, тому знав, що якби ти був у мережі, ти би швидше. Довелося видалити моє sedрішення (це трапляється, я знаю). XD
kos

У чому сенс posзмінної? Я розумію, він повертає вказівник на персонажа lineз символом нового рядка, але ти, схоже, ніколи не використовуєш його. І якщо ви не знайдете його, ви просто встановите його рівним \0.
користувач1717828

@ user1717828 Якщо я знайду це, я заміню його на \0( strchr()повертає вказівник NULL, якщо символ не знайдено). Суть полягає в заміні кожного нового рядка в кінці кожного рядка \0таким чином, щоб новий рядок ніколи не рахувався strlen(): це так, що довжину завжди можна порівняти з 6, незалежно від потенційного відсутнього нового рядка в останньому рядку. Я знаю, що по-різному лише останній рядок був би більш ефективним. Я, мабуть, оновлю це пізніше.
kos

1
@tripleee Ідея полягала в тому, щоб додати рішення, корисне для чогось більшого, ніж разова робота, або для ще більших файлів, але : я протестував grepрішення на тому ж файлі, і це насправді швидше (можливо, тому, що strlen()тут не найкраща ідея) . Я спробую використати getchar()цикл, щоб перевірити замість нього лише перший символ N, я думаю, що це повинно помітно покращити його. І так, будь-який рядок по довжині буфера просто розрізається на довжину буфера.
kos

2

Ви можете використовувати Vim в режимі Ex:

ex -sc 'v/\v.{6}/d' -cx file
  1. \v увімкніть магію

  2. .{6} знайти рядки з 6 і більше символами

  3. v інвертувати вибір

  4. d видалити

  5. x зберегти і закрити


1

Рубіновий розчин:

$ cat input.txt                                                                                                          
abcdef
abc
abcdefghijk

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

Проста ідея: перенаправляти файл у stdin рубіну та друкувати рядок із stdin, лише якщо його довжина більша або дорівнює 6

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.