Видаліть із файлу зайві рядки заголовка, за винятком першого рядка


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 за те, що побічно вказав, що нещодавно позикс видалили -1 синтаксис з голови, на користь -n 1.
Джефф Шаллер

3
@JeffSchaller, нещодавно, як і 12 років тому. І head -1застаріла десятиліттями до цього.
Стефан Шазелас

36

Можна використовувати

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

Це видалить рядки з ідентифікатором, починаючи з другого рядка.


3
приємно; або якщо бути більш конкретним у відповідності з малюнком, sed '2,${/^ID Data1 Data2$/d;}' file(звичайно , використовуючи потрібну кількість пробілів між стовпцями)
Jeff Schaller

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

Не з / sedс, s, ні.
mikeserv

aaaand -i для перемоги на місці редагування.
користувач2066657

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


@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оцінюється як істинний (якщо поточний рядок $0не такий, як x), рядок буде надруковано, оскільки дією за замовчуванням для awk на справжні вирази є друк. Перший рядок ( 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 добре, я буду проклятий! Дякую, я про це не знав. Виправлено зараз.
тердон

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}частини; in awk, якщо тестовий стан оцінено як істинне, передбачається, що рядок буде надруковано.
  • | 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 трохи елегантніше, ніж @terdon:

perl -i -p -e 's/^ID.*$//s if $. > 1' file

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
Дякуємо, що додали цю примху. Це буде вкрай корисно в майбутньому, оскільки більшість моїх конвеєрів вимагають з'єднання та об’єднання файлів з окремих зразків.
Гай Август
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.