Как создать массив уникальных элементов из строки / массива в Bash?

8

Если у меня есть строка «1 2 3 2 1» - или массив [1,2,3,2,1] - как я могу выбрать уникальные значения, т.е.

"1 2 3 2 1" produces "1 2 3" 

или

[1,2,3,2,1] produces [1,2,3]

Похоже на uniq, но uniq, похоже, работает на целых строках, а не на шаблонах внутри строки ...

Майкл Даррант
источник

Ответы:

4

С GNU awk(это также сохраняет оригинальный порядок)

printf '%s\n' "1 2 3 2 1" | awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}'
1 2 3 

Чтобы readв bashмассив

read -ra arr<<<$(printf '%s\n' "1 2 3 2 1" |
 awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}')
printf "%s\n"  "${arr[@]}"
1
2
3
Iruvar
источник
Как я могу сделать этот массив?
Майкл Даррант
@MichaelDurrant, если вы имеете в виду bashмассив, добавил способ
iruvar
Смотрите здесь, если ваш массив содержит пробелы
Том Хейл
@iruvar не могли бы вы объяснить, что на самом деле это значит? Я новичок в сценариях awk, и было бы полезно, если бы вы могли уточнить, что на самом деле происходит, когда вы говорите это! a [$ 0] ++
Abhishek
@iruvar, если не представляется возможным объяснить в комментариях любой веб-сайт, объясняющий, по крайней мере, приведенный выше синтаксис, было бы полезно.
Абхишек
9

Если вы используете zsh:

$ array=(1 2 3 2 1)
$ echo ${(u)array[@]}
1 2 3

или (если KSH_ARRAYSопция не установлена) даже

$ echo ${(u)array}
1 2 3
jimmij
источник
1
Если массив может содержать пустые элементы, вы должны использовать "${(u)array[@]}"или "${(@u)array}"вместо (обратите внимание на кавычки).
Стефан Шазелас
Я использую zsh 5.1.1 (x86_64-ubuntu-linux-gnu) и ${(u)array}работает, даже если массив пуст или содержит пустую строку, без кавычек.
kiamlaluno
4

Для массива с произвольными значениями это довольно сложно, так bashкак для этого нет встроенного оператора.

bash однако случается, что он не поддерживает сохранение символов NUL в своих переменных, поэтому вы можете использовать это для передачи этого другим командам:

Эквивалент zsh's:

new_array=("${(@u}array}")

на недавней системе GNU, может быть:

eval "new_array=($(
  printf "%s\0" "${array[@]}" |
    LC_ALL=C sort -zu |
    xargs -r0 bash -c 'printf "%q\n" "$@"' sh
  ))"

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

unset hash
typeset -A hash
for i in "${array[@]}"; do
  hash[$i]=
done
new_array=("${!hash[@]}")

С bash 4.4 и новее и с GNU sort:

readarray -td '' new_array < <(
  printf '%s\0' "${array[@]}" | LC_ALL=C sort -zu)

Порядок элементов не будет одинаковым в этих разных решениях.

С tcsh:

set -f new_array = ($array:q)

Сохранить Будет ли е IRST элемент ( a b a=> a b) , как zsh«S (u)флага расширения.

set -l new_array = ($array:q)

Сохранит последний ( a b a=> b a). Однако те удаляют пустые элементы из массива.

Стефан Шазелас
источник
1

Это решение сработало для меня.

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

Выше приведено 1 2 3 в качестве выхода.

Более короткая версия, предложенная Костасом, может быть такой:

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

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

IFS=$' '
arr=($(printf "%s\n" "${ids[@]}" | sort -u | tr '\n' ' '))
unset IFS

Теперь, когда я включаю эхо arr, это вывод, который я получаю.

echo "${arr[@]}"
1 2 3

Ссылки

https://stackoverflow.com/a/13648438/1742825 https://stackoverflow.com/a/9449633/1742825

Рамеш
источник
@ Костас, спасибо. Я включил это в ответ.
Рамеш
Как я могу сделать конечный результат массивом?
Майкл Даррант
@MichaelDurrant, пожалуйста, посмотрите обновленный ответ и дайте мне знать, если это хорошо.
Рамеш
Если вы хотите поместить результат в массив, вы можете удалить последнюю командуtr '\n' ' '
Costas
0

Чтобы сделать это целиком в оболочке и поместить результат в массив,

declare -A seen
for word in one two three two one
do
        if [ ! "${seen[$word]}" ]
        then
                result+=("$word")
                seen[$word]=1
        fi
done
echo "${result[@]}"

Словами: если мы еще не видели данное слово, добавьте его в resultмассив и отметьте его как увиденное. Как только слово было замечено, игнорируйте последующие появления этого слова.

Скотт
источник
2
Обратите внимание , что вам нужно , unset seenпрежде чем declare -A seenв случае $seenбыло определено ранее (даже в качестве скалярной переменной из окружающей среды).
Стефан Шазелас