Удалить лишние строки заголовка из файла, кроме первой строки

18

У меня есть файл, который выглядит как этот пример игрушки. В моем текущем файле 4 миллиона строк, около 10 из которых мне нужно удалить.

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
ID  Data1  Data2
4    100    100
ID  Data1  Data2
5    200    200

Я хочу удалить строки, которые выглядят как заголовок, за исключением первой строки.

Конечный файл:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200

Как я могу это сделать?

Гай Август
источник

Ответы:

26
header=$(head -n 1 input)
(printf "%s\n" "$header";
 grep -vFxe "$header" input
) > output
  1. захватить строку заголовка из входного файла в переменную
  2. напечатать заголовок
  3. обработать файл, grepчтобы пропустить строки, соответствующие заголовку
  4. захватить вывод из вышеупомянутых двух шагов в выходной файл
Джефф Шаллер
источник
2
или, возможно,{ IFS= read -r head; printf '%s\n' "$head"; grep -vF "$head" ; } <file
iruvar
Оба хороших дополнения. Спасибо don_crissti за косвенное указание на то, что posix недавно удалил из головы синтаксис -1, в пользу -n 1.
Джефф Шаллер
3
@JeffSchaller, недавно, как и 12 лет назад. И head -1был устаревшим на протяжении десятилетий до этого.
Стефан Шазелас
36

Ты можешь использовать

sed '2,${/ID/d;}'

Это удалит строки с идентификатором, начиная со строки 2.

bkmoney
источник
3
красивый; или, если быть более точным, с сопоставлением с образцом sed '2,${/^ID Data1 Data2$/d;}' file(конечно , используя правильное количество пробелов между столбцами)
Джефф Шаллер
Хм, я думал, что вы можете пропустить точку с запятой только для 1 команды, но хорошо.
bkmoney
Не без здравого sedсмысла, нет.
mikeserv
aaaand -i за победу редактирования на месте.
user2066657
4
Илиsed '1!{/ID/d;}'
Стефан Шазелас
10

Для тех, кто не любит фигурные скобки

sed -e '1n' -e '/^ID/d'
  • nозначает номер passстроки1
  • d удалить все совпадающие строки, которые начинаются с ^ID
Костас
источник
5
Это также может быть сокращено до sed '1n;/^ID/d'имени файла. просто предложение
Валентин Байрами
Обратите внимание, что при этом также будут напечатаны строки, подобные IDfooкоторым не совпадают с заголовком (в этом случае вряд ли что-то изменится , но вы никогда не узнаете).
Terdon
6

Вот веселый Вы можете использовать sedнепосредственно для удаления всех копий первой строки и оставить все остальное на месте (включая саму первую строку).

sed '1{h;n;};G;/^\(.*\)\n\1$/d;s/\n.*$//' input

1{h;n;}помещает первую строку в область удержания, печатает ее и читает следующую строку, пропуская остальные sedкоманды для первой строки. (Он также пропускает этот первый 1тест для второй строки , но это не имеет значения, поскольку этот тест не был бы применен ко второй строке.)

G добавляет новую строку, за которой следует содержимое области удержания, в пространство шаблона.

/^\(.*\)\n\1$/dудаляет содержимое пространства шаблона (таким образом пропуская к следующей строке), если часть после новой строки (то есть то, что было добавлено из области удержания) точно совпадает с частью перед новой строкой. Здесь строки, которые дублируют заголовок, будут удалены.

s/\n.*$// удаляет часть текста, которая была добавлена G командой, так что распечатывается только строка текста из файла.

Однако, поскольку регулярное выражение является дорогостоящим, несколько более быстрый подход состоял бы в том, чтобы использовать то же условие (отрицание) и Pповторную печать до новой строки, если часть после новой строки (то есть то, что было добавлено из пространства удержания) не совсем соответствует части перед новой строкой, а затем безоговорочно удалите пространство шаблона:

sed '1{h;n;};G;/^\(.*\)\n\1$/!P;d' input

Вывод, если дан ваш ввод:

ID  Data1  Data2
1    100    100
2    100    200
3    200    100
4    100    100
5    200    200
Wildcard
источник
Связанный: vi.stackexchange.com/q/6269/4676
подстановочный
@don_crissti, интересное дополнение; Благодарность! Я, вероятно, выбрал бы более длинный, но эквивалентный sed '1{h;n;};G;/^\(.*\)\n\1$/d;P;d' input; как-то мне легче читать. :)
Wildcard
Также связано: unix.stackexchange.com/a/417736/135943
Wildcard
5

Вот еще пара вариантов, которые не требуют, чтобы вы знали первую строку заранее:

perl -ne 'print unless $_ eq $k; $k=$_ if $.==1; 

-nФлаг говорит Perl для цикла по его входному файлу, сохраняя каждую строку как $_. $k=$_ if $.==1;Сохраняет первую линию ( $.номер строки, так $.==1будет верно только для 1 - й линии) , как $k. В print unless $k eq $_печатает текущую строку , если это не то же самое , как один спасен в $k.

В качестве альтернативы, то же самое в awk:

awk '$0!=x;(NR==1){x=$0}' file 

Здесь мы проверяем, совпадает ли текущая строка с той, что сохранена в переменной x. Если тест $0!=xоценивается как true (если текущая строка $0не совпадает с x), строка будет напечатана, потому что действие по умолчанию для awk в выражениях true - это печать. Первая строка ( NR==1) сохраняется как x. Поскольку это делается после проверки соответствия текущей строки x, это гарантирует, что первая строка также будет напечатана.

Тердон
источник
Мне нравится не знать идею первой строки, поскольку она делает ее обобщенным сценарием для вашей панели инструментов.
Марк Стюарт
1
этот метод awk создает пустую / ложную запись массива для каждой отдельной строки; для линий 4M, если все другие (не ясно из Q) и довольно короткие (кажется так), это, вероятно, хорошо, но если есть намного больше или более длинные строки, это может разбиться или умереть. !($0 in a)тестирует без создания и избегает этого, или awk может выполнить ту же логику, что и для perl: '$0!=x; NR==1{x=$0}'или если строка заголовка может быть пустой'NR==1{x=$0;print} $0!=x'
dave_thompson_085
1
@ dave_thompson_085 где создается массив для каждой строки? Вы имеете в виду !a[$0]? Зачем это создает запись в a?
Terdon
1
Потому что так работает awk; см. gnu.org/software/gawk/manual/html_node/… особенно «ПРИМЕЧАНИЕ».
dave_thompson_085
1
@ dave_thompson_085 хорошо, я буду проклят! Спасибо, я не знал об этом. Исправлено сейчас.
Terdon
4

AWK - вполне приличный инструмент для этой цели. Вот пример запуска кода:

$ awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt | head -n 10                                
ID  Data1  Data2
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100
1    100    100
     100    200
3    200    100

Сломать :

  • NR == 1 {print} говорит нам напечатать первую строку текстового файла
  • NR != 1 && $0!~/ID Data1 Data2/ Логический оператор &&указывает AWK напечатать строку, которая не равна 1 и не содержит ID Data1 Data2. Обратите внимание на отсутствие {print}части; в awk, если тестовое условие оценивается как true, предполагается, что строка должна быть напечатана.
  • | head -n 10это всего лишь небольшое дополнение, ограничивающее вывод только первыми 10 строками. Не относится к самой AWKчасти, используется только для демонстрационных целей.

Если вы хотите это в файле, перенаправьте вывод команды, добавив > newFile.txtв конце команды, например, так:

awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > newFile.txt

Как это держится? Довольно неплохо на самом деле:

$ time awk 'NR == 1 {print} NR != 1 && $0!~/ID  Data1  Data2/' rmLines.txt > /dev/null                            
    0m3.60s real     0m3.53s user     0m0.06s system

Примечание

Сгенерированный пример файла был сделан для того, чтобы выполнить цикл от одного до миллиона и напечатать первые четыре строки вашего файла (таким образом, 4 строки на миллион равны 4 миллионам строк), что, кстати, заняло 0,09 секунды.

awk 'BEGIN{ for(i=1;i<=1000000;i++) printf("ID  Data1  Data2\n1    100    100\n     100    200\n3    200    100\n");  }' > rmLines.txt
Сергей Колодяжный
источник
Обратите внимание, что при этом также будут напечатаны строки, подобные ID Data1 Data2 fooкоторым не совпадают с заголовком (в этом случае вряд ли что-то изменится , но вы никогда не узнаете).
Terdon
@terdon да, совершенно верно. Однако ОП указал только один шаблон, который они хотят удалить, и его пример, кажется, подтверждает это
Сергей Колодяжный
3

Awk, адаптируясь к любому заголовку автоматически:

awk '( FNR == 1) {header=$0;print $0;}
     ( FNR > 1) && ($0 != header) { print $0;}'  file1  file2 ....

то есть, в первой строке получите заголовок и напечатайте его, и последующая строка, ОТЛИЧНАЯ от этого заголовка, будет напечатана.

FNR = количество записей в текущем файле, так что вы можете иметь несколько файлов, и он будет делать то же самое в каждом из них.

Оливье Дюлак
источник
2

Для полноты Perl решение IMO немного более элегантно, чем @terdon:

perl -i -p -e 's/^ID.*$//s if $. > 1' file
KWubbufetowicz
источник
1
Ах, но суть в том, чтобы избежать необходимости указывать шаблон и вместо этого читать его из первой строки. Ваш подход просто удалит любую строку, которая начинается с ID. У вас нет гарантии, что это не удалит строки, которые должны быть сохранены. Так как вы воспитали элегантность, gбессмысленно использовать ^и $. На самом деле, все ваши варианты m///здесь бесполезны, кроме s; они активируют функции, которые вы не используете. Так что $, s/^ID.*//sсделал бы то же самое.
Terdon
@terdon, достаточно справедливо. Ваш намного универсальнее!
KWubbufetowicz
2

Просто немного отодвинем вопрос ... похоже, что ваш вклад сам по себе является результатом объединения нескольких файлов TSV. Если вы можете выполнить резервное копирование шага в конвейере обработки (если вы владеете этим или можете общаться с теми, кто это делает), вы можете использовать инструмент с поддержкой заголовков, чтобы объединить данные в первую очередь и тем самым устранить проблему необходимости удалить лишние строки заголовка.

Например, используя Миллер :

$ cat f1.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
$ cat f2.tsv
ID  Data1 Data2
4 100 100
$ cat f3.tsv
ID  Data1 Data2
5 200 200

$ cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
ID  Data1 Data2
4 100 100
ID  Data1 Data2
5 200 200

$ mlr --tsvlite cat f1.tsv f2.tsv  f3.tsv
ID  Data1 Data2
1 100 100
2 100 200
3 200 100
4 100 100
5 200 200
Джон Керл
источник
1
Спасибо за добавление этого лакомого кусочка. Это будет чрезвычайно полезно в будущем, так как большинство моих конвейеров требуют объединения и объединения файлов из отдельных образцов.
Гай Август