Исключить строки на основе длинного списка номеров строк
У меня есть длинный список номеров строк (35389208), которые мне не нужны в моем файле. Под номером строки я подразумеваю строку в моем файле (например, строка 277). Мой список номеров строк, которые я не хочу, выглядит так:
277
278
279
280
289
290
291
292
321
322
....
Каков наилучший способ исключить эти номера строк из моего файла? Решение в Perl Sed или AWK (или что-нибудь еще).
5 ответов
Если вы можете прочитать все номера строк в памяти, вы можете сделать это следующим образом: 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
Это может работать для вас (GNU sed):
sed 's/.*/&d/' line-numbers-to-delete-file | sed -f - file-to-be-shortened
Сгенерируйте скрипт из файла, содержащего строки для удаления, и передайте его экземпляру sed, используя файл, который вы хотите сократить в качестве входных данных.
Вы можете попробовать использовать регулярные выражения с 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)
Обратите внимание, что за исключением дополнительного кода в попытке 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
, Довольно хорошо!
Если вы не хотите печатать строки из файла строк (как это хотел сделать OP), слегка измените команду так, чтобы цифры белья были удалены, а не напечатаны, и напечатайте все остальные строки вместо их удаления (-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
, Что понятно.