Как получить уникальные значения из массива в Bash?

У меня почти такой же вопрос, как здесь.

У меня есть массив, содержащий aa ab aa ac aa ad и т.д. Теперь я хочу выделить все уникальные элементы из этого массива. Думал, это было бы просто с sort | uniq или с sort -u, как они упомянули в этом другом вопросе, но ничего не изменилось в массиве... Код:

echo `echo "${ids[@]}" | sort | uniq`

Что я делаю неправильно?

+70
источник поделиться
13 ответов

Немного вздор, но это должно быть сделано:

echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '

Чтобы сохранить отсортированные уникальные результаты обратно в массив, выполните присвоение массива:

sorted_unique_ids=($(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '))

Если ваша оболочка поддерживает herestrings (следует bash), вы можете сэкономить процесс echo, изменив его на:

tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' '

Вход:

ids=(aa ab aa ac aa ad)

Выход:

aa ab ac ad

Объяснение:

  • "${ids[@]}" - Синтаксис для работы с массивами оболочки, используется ли он как часть echo или в виде строки. Часть @ означает "все элементы в массиве"
  • tr ' ' '\n' - конвертировать все пробелы в переводы строк. Потому что ваш массив рассматривается оболочкой как элементы в одной строке, разделенные пробелами; и потому что сортировка ожидает, что вход будет в отдельных строках.
  • sort -u - сортировать и сохранять только уникальные элементы
  • tr '\n' ' ' - преобразовать добавленные нами ранее строки в пробелы.
  • $(...) - Подстановка команд
  • Кроме того: tr ' ' '\n' <<< "${ids[@]}" является более эффективным способом: echo "${ids[@]}" | tr ' ' '\n'
+104
источник

Если вы используете Bash версии 4 или выше (что должно быть в любой современной версии Linux), вы можете получить уникальные значения массива в bash, создав новый ассоциативный массив, который содержит каждое из значений исходного массива. Что-то вроде этого:

$ a=(aa ac aa ad "ac ad")
$ declare -A b
$ for i in "${a[@]}"; do b["$i"]=1; done
$ printf '%s\n' "${!b[@]}"
ac ad
ac
aa
ad

Это работает, потому что в массиве каждый ключ может появляться только один раз. Когда цикл for достигает второго значения aa в a[2], он перезаписывает b[aa] который изначально был установлен для a[0].

Делать вещи в native bash можно быстрее, чем с помощью конвейеров и внешних инструментов, таких как sort и uniq.

Если вы чувствуете себя уверенно, вы можете избежать цикла for, используя возможность printf перерабатывать его формат для нескольких аргументов, хотя для этого, по-видимому, требуется eval. (Перестаньте читать сейчас, если вы в порядке с этим.)

$ eval b=( $(printf ' ["%s"]=1' "${a[@]}") )
$ declare -p b
declare -A b=(["ac ad"]="1" [ac]="1" [aa]="1" [ad]="1" )

Причина, по которой это решение требует eval заключается в том, что значения массива определяются перед разделением слов. Это означает, что выходные данные подстановки команд рассматриваются как одно слово, а не как набор пар ключ = значение.

Хотя он использует подоболочку, он использует только встроенные функции bash для обработки значений массива. Обязательно оцените использование eval критическим взглядом. Если вы не уверены на 100%, что chepner, glenn jackman или greycat не найдут ошибку в вашем коде, используйте вместо этого цикл for.

+22
источник
другие ответы

Связанные вопросы


Похожие вопросы

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

printf "%s\n" "${IDS[@]}" | sort -u

Пример:

~> IDS=( "aa" "ab" "aa" "ac" "aa" "ad" )
~> echo  "${IDS[@]}"
aa ab aa ac aa ad
~>
~> printf "%s\n" "${IDS[@]}" | sort -u
aa
ab
ac
ad
~> UNIQ_IDS=($(printf "%s\n" "${IDS[@]}" | sort -u))
~> echo "${UNIQ_IDS[@]}"
aa ab ac ad
~>
+12
источник

Если ваши элементы массива имеют пробел или какой-либо другой специальный символ оболочки (и можете ли вы быть уверены, что они этого не делают?), то чтобы захватить их прежде всего (и вы всегда должны это делать), выражайте массив в двойных кавычках! например "${a[@]}". Bash будет буквально интерпретировать это как "каждый элемент массива в отдельном аргументе". Внутри Bash это всегда всегда работает всегда.

Затем, чтобы получить отсортированный (и уникальный) массив, мы должны преобразовать его в формат, который понимает и сможет преобразовать его в элементы массива Bash. Это лучшее, что я придумал:

eval a=($(printf "%q\n" "${a[@]}" | sort -u))

К сожалению, это не удается в частном случае пустого массива, превратив пустой массив в массив из 1 пустого элемента (поскольку printf имеет 0 аргументов, но все равно печатает, как будто он имеет один пустой аргумент - см. объяснение). Таким образом, вы должны поймать это в if или something.

Объяснение: Формат% q для printf "shell escapes" напечатанный аргумент, таким образом, как Bash может восстанавливаться в чем-то вроде eval! Поскольку каждый элемент печатается оболочкой, экранированной на собственной строке, единственным разделителем между элементами является новая строка, а назначение массива берет каждую строку как элемент, анализируя экранированные значения в литеральный текст.

например.

> a=("foo bar" baz)
> printf "%q\n" "${a[@]}"
'foo bar'
baz
> printf "%q\n"
''

Значение eval необходимо для того, чтобы отключить выделение каждого значения, возвращаемого в массив.

+11
источник

'sort' может использоваться для упорядочения вывода цикла for:

for i in ${ids[@]}; do echo $i; done | sort

и устранить дубликаты с помощью "-u":

for i in ${ids[@]}; do echo $i; done | sort -u

Наконец, вы можете просто перезаписать свой массив уникальными элементами:

ids=( `for i in ${ids[@]}; do echo $i; done | sort -u` )
+8
источник

этот порядок также сохранит:

echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'

и изменить исходный массив с уникальными значениями:

ARRAY=($(echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'))
+2
источник

Чтобы создать новый массив, состоящий из уникальных значений, убедитесь, что ваш массив не пуст, выполните одно из следующих действий:

Удалить повторяющиеся записи (с сортировкой)

readarray -t NewArray < <(printf '%s\n' "${OriginalArray[@]}" | sort -u)

Удалить повторяющиеся записи (без сортировки)

readarray -t NewArray < <(printf '%s\n' "${OriginalArray[@]}" | awk '!x[$0]++')

Предупреждение. Не пытайтесь делать что-то вроде NewArray=( $(printf '%s\n' "${OriginalArray[@]}" | sort -u) ). Он разбивается на пробелы.

+2
источник

номер кошки

1 2 3 4 4 3 2 5 6

вывести строку в столбец: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}'

1
2
3
4
4
3
2
5
6

найти дубликаты записей: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk 'x[$0]++' cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk 'x[$0]++'

4
3
2

Заменить повторяющиеся записи: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk '!x[$0]++' cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk '!x[$0]++'

1
2
3
4
5
6

Найти только записи Uniq: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i|"sort|uniq -u"} cat number.txt | awk '{for(i=1;i<=NF;i++) print $i|"sort|uniq -u"}

1
5
6
+1
источник

Если вы хотите решение, которое использует только внутренние элементы bash, вы можете установить значения как ключи в ассоциативном массиве, а затем извлечь ключи:

declare -A uniqs
list=(foo bar bar "bar none")
for f in "${list[@]}"; do 
  uniqs["${f}"]=""
done

for thing in "${!uniqs[@]}"; do
  echo "${thing}"
done

Это приведет к выводу

bar
foo
bar none
+1
источник

Без потери исходного заказа:

uniques=($(tr ' ' '\n' <<<"${original[@]}" | awk '!u[$0]++' | tr '\n' ' '))
0
источник

Попробуйте это, чтобы получить значения uniq для первого столбца в файле

awk -F, '{a[$1];}END{for (i in a)print i;}'
0
источник

Другой вариант для работы со встроенным пробелом, это разделение нулями с printf, выделение с помощью sort, а затем использование цикла для упаковки обратно в массив:

input=(a b c "$(printf "d\ne")" b c "$(printf "d\ne")")
output=()

while read -rd $'' element
do 
  output+=("$element")
done < <(printf "%s\0" "${input[@]}" | sort -uz)

В конце этого, input и output содержат желаемые значения (при условии, что порядок не важен):

$ printf "%q\n" "${input[@]}"
a
b
c
$'d\ne'
b
c
$'d\ne'

$ printf "%q\n" "${output[@]}"
a
b
c
$'d\ne'
0
источник
# Read a file into variable
lines=$(cat /path/to/my/file)

# Go through each line the file put in the variable, and assign it a variable called $line
for line in $lines; do
  # Print the line
  echo $line
# End the loop, then sort it (add -u to have unique lines)
done | sort -u
-1
источник

Посмотрите другие вопросы по меткам или Задайте вопрос