3

У меня есть длинный список номеров строк (35389208), которые мне не нужны в моем файле. Под номером строки я подразумеваю строку в моем файле (например, строка 277). Мой список номеров строк, которые я не хочу, выглядит так:

277
278
279
280
289
290
291
292
321
322
....

Каков наилучший способ исключить эти номера строк из моего файла? Решение в Perl Sed или AWK (или что-нибудь еще).

5 ответов5

1

Вы можете попробовать использовать регулярное выражение с sed:

sed '/^[0-9]*$/d' filename.txt

Это удалит строки, в которых есть только цифры из вашего файла.

Следующий скрипт Perl удалит n - ую строку из файла input.txt и выведет остаток в stdout . Номера строк могут быть указаны в line_numbers.txt:

#!/usr/bin/perl

my @lines_to_exclude;

open(my $fh_line_numbers, "<", "line_numbers.txt") or die "Failed to open file: $!\n";
while(<$fh_line_numbers>) { 
  chomp; 
  push @lines_to_exclude, $_;
} 
close $fh_line_numbers;

my $linecounter = 1;

open (my $fh_datafile, '<', 'input.txt') or die "Cannot open $filename: $!";

while ( my $line = <$fh_datafile> ) {

  if ( ! ( $linecounter ~~ @lines_to_exclude ) ) {
    print $line;
  }

  $linecounter++;
}

close($fh_datafile);

(оператор ~~ доступен только в perl> = 5.10)

1

Если вы можете прочитать все номера строк в памяти, вы можете сделать это с помощью awk:

awk 'FNR == NR { h[$1]; next } !(FNR in h)' line-numbers.txt input.txt

Если у вас ограниченная память и ваш файл line-numbers.txt отсортирован по номерам, вы можете сделать это следующим образом:

удалить-lines.awk

BEGIN {  
  lines_file = "line-numbers.txt"
  if(!(getline n < lines_file)) { 
    print "Unable to open lines file " lines_file > "/dev/stderr" 
    exit 
  } 
} 

FNR != n

FNR == n {
  getline n < lines_file
}

Запустите это так:

awk -f delete-lines.awk input.txt

Тестирование, где line-numbers.txt содержит:

277
278
279
280
289
290
291
292
321
322

и input.txt представлен последовательностью seq 325 .

Сначала с номерами строк в памяти:

seq 325 | awk 'FNR == NR { h[$1]; next } !(FNR in h)' line-numbers.txt -

затем с номерами строк, читаемыми по одному за раз:

seq 325 | awk -f delete-lines.awk -

Выведите в обоих случаях (строки с 1 по 274 опущены):

.
.
.
275
276
281
282
283
284
285
286
287
288
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
323
324
325
0

Удалить номер в начале строки

sed 's/^[0-9]*//g' filename.txt
0

Это может работать для вас (GNU sed):

sed 's/.*/&d/' line-numbers-to-delete-file | sed -f - file-to-be-shortened

Сгенерируйте скрипт из файла, содержащего строки для удаления, и передайте его экземпляру sed, используя файл, который вы хотите сократить в качестве входных данных.

0

Обратите внимание, что за исключением дополнительного кода в попытке 2, весь код фактически противоположен тому, что запрашивал OP. Как видно из попытки 2, команды легко адаптировать.

У меня был текстовый файл с 1.108.752 строками, размером около 83 МБ. Я хотел получить 46.744 строки от 15-й до 1.108.716-й строки, что в среднем примерно на каждую 24-ю строку.

ТЛ; др;

Вторая попытка быстрее, чем первая. Третий работает только для меньшего количества строк.

Первая попытка (плохо)

Для каждой строки, которую я хочу, sed читает строки из начала текстового файла, но не печатает их (-n). Когда он достигнет нужной мне строки, выведите его (p), а затем закройте (q) вместо чтения до конца файла. Затем сделайте это снова для следующего белья.

Очевидно, что это занимает немного больше времени для каждого запуска, потому что sed должен проходить больше строк, чем раньше.

Если бы я рассчитал это право, в моем случае это заняло бы около 307332472188 проходов через текстовый файл в целом. Боже мой

Обратите внимание, что для этого подхода порядок строк не имеет значения в файле белья.

while read line; do
    sed -n "${line}{p;q}" "${INFILE}"
done

Результаты синхронизации: 2568.80s user 256.10s system 92% cpu 51:00.37 total . Не хорошо.

Вторая попытка (лучше)

Это читает номера белья из файла и добавляет p (опять же, для печати этой строки). Эта строка передается следующему sed , который читает из файла (-f), который здесь представляет собой STDIN записываемый как - , который каждый раз является выводом первого sed , который фактически является номером белья, который будет напечатан:

sed 's/$/p/' "${LINENUMS}" | sed -n -f - "${INFILE}"

Результаты синхронизации: 146.54s user 0.18s system 100% cpu 2:26.70 total . Довольно хорошо!

Если вы хотите , чтобы не печатать строки из linefile (как OP хотел сделать), немного изменить команду так , что номера строк в настоящее время d eleted вместо р rinted и печатать все остальные строки вместо их удаления (-n):

sed 's/$/d/' "${LINENUMS}" | sed -f - "${INFILE}"

Третья попытка (баддер)

Это не сработало для меня, потому что у меня было слишком много строк, которые я хотел извлечь. Это должно работать для (намного) меньшего количества строк, хотя, но я не знаю предел этому.

Я попытался создать длинную строку для sed, что, как я ожидал, приведет к тому, что sed будет проходить через файл только один раз (!), Не печатая ничего, кроме номеров белья из строки:

sed -n "12p;15p;24p;345p;...;12345;" ${INFILE}"

но это привело бы к строке длиной около 420076 символов, которая при загрузке в sed просто приводила к sed: Argument list is too long . Что понятно.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .