Есть ли способ «uniq» по столбцу? - PullRequest
169 голосов
/ 16 декабря 2009

У меня есть CSV-файл, подобный этому:

stack2@example.com,2009-11-27 01:05:47.893000000,example.net,127.0.0.1
overflow@example.com,2009-11-27 00:58:29.793000000,example.net,255.255.255.0
overflow@example.com,2009-11-27 00:58:29.646465785,example.net,256.255.255.0
...

Мне нужно удалить дубликаты электронных писем (всю строку) из файла (т.е. одну из строк, содержащих overflow@example.com в приведенном выше примере) Как использовать uniq только для поля 1 (через запятую)? Согласно man, uniq не имеет опций для столбцов.

Я пробовал что-то с sort | uniq, но это не работает.

Ответы [ 8 ]

287 голосов
/ 16 декабря 2009
sort -u -t, -k1,1 file
  • -u для уникального
  • -t, таким образом, запятая является разделителем
  • -k1,1 для ключевого поля 1

Результат теста:

overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1 
86 голосов
/ 16 декабря 2009
awk -F"," '!_[$1]++' file
  • -F устанавливает разделитель полей.
  • $1 - это первое поле.
  • _[val] ищет val в хэше _ (обычная переменная).
  • ++ инкремент и возврат старого значения.
  • ! возвращает логическое нет.
  • в конце есть неявная печать.
14 голосов
/ 21 января 2014

Рассмотреть несколько столбцов.

Сортировка и выдача уникального списка на основе столбца 1 и столбца 3:

sort -u -t : -k 1,1 -k 3,3 test.txt
  • -t : двоеточие - разделитель
  • -k 1,1 -k 3,3 на основе столбца 1 и столбца 3
9 голосов
/ 16 декабря 2009

или если вы хотите использовать uniq:

<mycvs.cvs tr -s ',' ' ' | awk '{print $3" "$2" "$1}' | uniq -c -f2

дает:

1 01:05:47.893000000 2009-11-27 tack2@domain.com
2 00:58:29.793000000 2009-11-27 overflow@domain2.com
1
4 голосов
/ 14 декабря 2015

Если вы хотите сохранить последний из дубликатов, который вы можете использовать

 tac a.csv | sort -u -t, -r -k1,1 |tac

Что было моим требованием

здесь

tac перевернет файл строка за строкой

0 голосов
/ 25 апреля 2017

Вот очень изящный способ.

Сначала отформатируйте содержимое так, чтобы столбец, который нужно сравнить по уникальности, имел фиксированную ширину. Один из способов сделать это - использовать awk printf со спецификатором ширины поля / столбца ("% 15s").

Теперь параметры uniq -f и -w можно использовать, чтобы пропустить предыдущие поля / столбцы и указать ширину сравнения (ширину столбцов).

Вот три примера.

В первом примере ...

1) Временно сделайте столбец интереса фиксированной шириной, большей или равной максимальной ширине поля.

2) Используйте параметр -f uniq, чтобы пропустить предыдущие столбцы, и используйте параметр -w uniq, чтобы ограничить ширину значением tmp_fixed_width.

3) Удалите конечные пробелы из столбца, чтобы «восстановить» его ширину (при условии, что заранее не было конечных пробелов).

printf "%s" "$str" \
| awk '{ tmp_fixed_width=15; uniq_col=8; w=tmp_fixed_width-length($uniq_col); for (i=0;i<w;i++) { $uniq_col=$uniq_col" "}; printf "%s\n", $0 }' \
| uniq -f 7 -w 15 \
| awk '{ uniq_col=8; gsub(/ */, "", $uniq_col); printf "%s\n", $0 }'

Во втором примере ...

Создание нового столбца uniq 1. Затем удалите его после применения фильтра uniq.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; printf "%15s %s\n", uniq_col_1, $0 }' \
| uniq -f 0 -w 15 \
| awk '{ $1=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'

Третий пример такой же, как второй, но для нескольких столбцов.

printf "%s" "$str" \
| awk '{ uniq_col_1=4; uniq_col_2=8; printf "%5s %15s %s\n", uniq_col_1, uniq_col_2, $0 }' \
| uniq -f 0 -w 5 \
| uniq -f 1 -w 15 \
| awk '{ $1=$2=""; gsub(/^ */, "", $0); printf "%s\n", $0 }'
0 голосов
/ 16 декабря 2009

Сортировав файл сначала по sort, вы можете применить uniq.

Кажется, что файл отсортирован очень хорошо:

$ cat test.csv
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

$ sort test.csv | uniq
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
overflow@domain2.com,2009-11-27 00:58:29.793000000,xx3.net,255.255.255.0
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1

Вы также можете использовать магию AWK:

$ awk -F, '{ lines[$1] = $0 } END { for (l in lines) print lines[l] }' test.csv
stack2@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack4@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
stack3@domain.com,2009-11-27 01:05:47.893000000,xx2.net,127.0.0.1
overflow@domain2.com,2009-11-27 00:58:29.646465785,2x3.net,256.255.255.0 
0 голосов
/ 16 декабря 2009

ну, проще, чем изолировать столбец с помощью awk, если вам нужно удалить все с определенным значением для данного файла, почему бы просто не выполнить grep -v:

например. удалить все со значением "col2" на втором месте строка: col1, col2, col3, col4

grep -v ',col2,' file > file_minus_offending_lines

Если этого недостаточно, поскольку некоторые строки могут быть неправильно удалены из-за возможного отображения соответствующего значения в другом столбце, вы можете сделать что-то вроде этого:

awk для выделения ошибочного столбца: например

awk -F, '{print $2 "|" $line}'

-F устанавливает поле, разделенное на ",", $ 2 означает столбец 2, за которым следует некоторый пользовательский разделитель, а затем вся строка. Затем вы можете отфильтровать, удалив строки, которые начинаются со значения сбоя:

 awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE

и затем раздеть содержимое перед разделителем:

awk -F, '{print $2 "|" $line}' | grep -v ^BAD_VALUE | sed 's/.*|//g'

(примечание: команда sed неаккуратная, поскольку не содержит экранирующих значений. Кроме того, шаблон sed должен быть чем-то вроде «[^ |] +» (т. Е. Что-либо, кроме разделителя). Но, надеюсь, это достаточно ясно .

...