Выберите уникальные или уникальные значения из списка в сценарии оболочки UNIX

238

У меня есть сценарий ksh, который возвращает длинный список значений, разделенных новой строкой, и я хочу видеть только уникальные / отличные значения. Это возможно сделать?

Например, скажем, мой вывод - суффиксы файлов в каталоге:

tar
gz
java
gz
java
tar
class
class

Я хочу увидеть список вроде:

tar
gz
java
class
brabster
источник

Ответы:

432

Вы можете захотеть взглянуть на uniqи sortприложениях.

./yourscript.ksh | сортировать | уник

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

РЕДАКТИРОВАТЬ:

Вопреки тому, что было опубликовано Аароном Дигуллой в отношении uniqпараметров командной строки:

Учитывая следующий вход:

класс
яс
яс
яс
бункер
бункер
Ява

uniq выведет все строки ровно один раз:

класс
яс
бункер
Ява

uniq -d выведет все строки, которые появляются более одного раза, и напечатает их один раз:

яс
бункер

uniq -u выведет все строки, которые появляются ровно один раз, и напечатает их один раз:

класс
Ява
Мэтью Шарли
источник
2
Просто к сведению опоздавших: @ AaronDigulla ответ с тех пор был исправлен.
mklement0
2
очень хороший момент, эта `сортировка необходима в этой командной строке, uniq удаляет только дубликаты строк, которые идут сразу после друг друга`, что я только что узнал !!
HattrickNZ
4
В GNU также sortесть -uверсия для предоставления уникальных значений.
Arthur2e5
Я выяснил, что uniqшвы для обработки только смежных линий (по крайней мере, по умолчанию) означает, что можно sortвводить до кормления uniq.
Стефан
85
./script.sh | sort -u

Это то же самое, что и ответ по оксиду , но более краткий.

gpojd
источник
6
Вы скромны: ваше решение также будет работать лучше (вероятно, заметно только при больших наборах данных).
mklement0
Я думаю, что это должно быть более эффективным, чем ... | sort | uniqпотому, что это выполняется за один выстрел
Адриан Антунес
10

Для больших наборов данных, где сортировка может быть нежелательна, вы также можете использовать следующий скрипт perl:

./yourscript.ksh | perl -ne 'if (!defined $x{$_}) { print $_; $x{$_} = 1; }'

Это в основном просто запоминает каждую строку вывода, чтобы он не выводил ее снова.

Он имеет преимущество перед « sort | uniq» решением в том, что не требуется предварительной сортировки.

paxdiablo
источник
2
Обратите внимание, что сортировка очень большого файла сама по себе не является проблемой сортировки; он может сортировать файлы, которые больше, чем доступная оперативная память + подкачка. Perl, OTOH, потерпит неудачу, если будет только несколько дубликатов.
Аарон Дигулла
1
Да, это компромисс в зависимости от ожидаемых данных. Perl лучше подходит для огромного набора данных со множеством дубликатов (не требуется дисковое хранилище). Огромный набор данных с несколькими дубликатами должен использовать сортировку (и дисковое хранилище). Небольшие наборы данных могут использовать либо. Лично я бы сначала попробовал Perl, переключился на сортировку, если она не удалась.
paxdiablo
Поскольку сортировка дает вам преимущество только в том случае, если она должна быть перенесена на диск.
paxdiablo
5
Это здорово, когда я хочу первое вхождение каждой строки. Сортировка сломала бы это.
Bluu
10

С Zsh вы можете сделать это:

% cat infile 
tar
more than one word
gz
java
gz
java
tar
class
class
zsh-5.0.0[t]% print -l "${(fu)$(<infile)}"
tar
more than one word
gz
java
class

Или вы можете использовать AWK:

% awk '!_[$0]++' infile    
tar
more than one word
gz
java
class
Димитр Радулов
источник
2
Умные решения, которые не включают сортировку ввода. Предостережения: очень умное, но загадочное awkрешение (см. Stackoverflow.com/a/21200722/45375 для объяснения) будет работать с большими файлами, если количество уникальных строк достаточно мало (так как уникальные строки хранятся в памяти ). zshРешение читает весь файл в память первым, которая не может быть вариантом с большими файлами. Кроме того, как написано, только строки без встроенных пробелов обрабатываются правильно; чтобы исправить это, используйте IFS=$'\n' read -d '' -r -A u <file; print -l ${(u)u}вместо этого.
mklement0
Верный. Или:(IFS=$'\n' u=($(<infile)); print -l "${(u)u[@]}")
Димитр Радулов
1
Спасибо, это проще (при условии, что вам не нужно устанавливать переменные, необходимые вне подоболочки). Мне любопытно, когда вам нужен [@]суффикс для ссылки на все элементы массива - кажется, что - по крайней мере, начиная с версии 5 - он работает без него; или вы просто добавили это для ясности?
mklement0
1
@ mklement0, ты прав! Я не думал об этом, когда писал пост. На самом деле, этого должно быть достаточно:print -l "${(fu)$(<infile)}"
Димитр Радулов
1
Фантастика, спасибо за обновление вашего поста - я позволил себе также исправить awkпример вывода.
mklement0
9

Труба их через sortи uniq. Это удаляет все дубликаты.

uniq -dдает только дубликаты, uniq -uдает только уникальные (дубликаты полос).

Аарон Дигулла
источник
должен сортировать сначала по внешнему виду
Brabster
1
Да, вы делаете. Точнее, вам нужно сгруппировать все повторяющиеся строки вместе. Сортировка делает это по определению;)
Мэтью Шарли
Кроме того, uniq -uэто НЕ поведение по умолчанию (подробности см. В редактировании моего ответа)
Мэтью Шарли,
7

С AWK вы можете сделать, я нахожу это быстрее, чем сортировать

 ./yourscript.ksh | awk '!a[$0]++'
Ajak6
источник
Это определенно мой любимый способ сделать работу, большое спасибо! Специально для больших файлов сортировка | uniq-solutions, вероятно, не то, что вам нужно.
Schmitzi
1

Уникальный, как и было запрошено (но не отсортировано);
использует меньше системных ресурсов для менее чем ~ 70 элементов (как проверено временем);
написано для ввода данных из stdin,
(или изменить и включить в другой скрипт):
(Bash)

bag2set () {
    # Reduce a_bag to a_set.
    local -i i j n=${#a_bag[@]}
    for ((i=0; i < n; i++)); do
        if [[ -n ${a_bag[i]} ]]; then
            a_set[i]=${a_bag[i]}
            a_bag[i]=$'\0'
            for ((j=i+1; j < n; j++)); do
                [[ ${a_set[i]} == ${a_bag[j]} ]] && a_bag[j]=$'\0'
            done
        fi
    done
}
declare -a a_bag=() a_set=()
stdin="$(</dev/stdin)"
declare -i i=0
for e in $stdin; do
    a_bag[i]=$e
    i=$i+1
done
bag2set
echo "${a_set[@]}"
FGrose
источник
0

Я получаю лучшие советы, чтобы получить не повторяющиеся записи в файле

awk '$0 != x ":FOO" && NR>1 {print x} {x=$0} END {print}' file_name | uniq -f1 -u
Мэри Марти
источник