Как получить уникальные значения из массива в Bash?
У меня почти такой же вопрос, как здесь.
У меня есть массив, содержащий aa ab aa ac aa ad
и т.д.
Теперь я хочу выделить все уникальные элементы из этого массива.
Думал, это было бы просто с sort | uniq
или с sort -u
, как они упомянули в этом другом вопросе, но ничего не изменилось в массиве...
Код:
echo `echo "${ids[@]}" | sort | uniq`
Что я делаю неправильно?
Ответы
Ответ 1
Немного вздор, но это должно быть сделано:
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'
Ответ 2
Если вы используете 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.
Ответ 3
Я понимаю, что на это уже был дан ответ, но он оказался очень высоким в результатах поиска, и это могло бы помочь кому-то.
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
~>
Ответ 4
Если ваши элементы массива имеют пробел или какой-либо другой специальный символ оболочки (и можете ли вы быть уверены, что они этого не делают?), то чтобы захватить их прежде всего (и вы всегда должны это делать), выражайте массив в двойных кавычках! например "${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 необходимо для того, чтобы отключить выделение каждого значения, возвращаемого в массив.
Ответ 5
'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` )
Ответ 6
этот порядок также сохранит:
echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'
и изменить исходный массив с уникальными значениями:
ARRAY=($(echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'))
Ответ 7
Чтобы создать новый массив, состоящий из уникальных значений, убедитесь, что ваш массив не пуст, выполните одно из следующих действий:
Удалить повторяющиеся записи (с сортировкой)
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) )
. Он разбивается на пробелы.
Ответ 8
номер кошки
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
Ответ 9
Если вы хотите решение, которое использует только внутренние элементы 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
Ответ 10
Без потери исходного заказа:
uniques=($(tr ' ' '\n' <<<"${original[@]}" | awk '!u[$0]++' | tr '\n' ' '))
Ответ 11
Попробуйте это, чтобы получить значения uniq для первого столбца в файле
awk -F, '{a[$1];}END{for (i in a)print i;}'
Ответ 12
Другой вариант для работы со встроенным пробелом, это разделение нулями с 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'
Ответ 13
# 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