如何从Bash中的数组中获取唯一值?

时间:2012-11-30 15:43:59

标签: linux arrays bash unique

我的问题与here几乎相同。

我有一个包含aa ab aa ac aa ad等的数组。 现在我想从这个数组中选择所有唯一元素。 想一想,对于sort | uniqsort -u,这在其他问题中提到的很简单,但数组中没有任何变化...... 代码是:

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

我做错了什么?

15 个答案:

答案 0 :(得分:98)

有点hacky,但这应该这样做:

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

要将已排序的唯一结果保存回数组,请执行Array assignment

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

如果您的shell支持herestringsbash应该),则可以通过将其更改为echo来保留tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' ' 进程:

ids=(aa ab aa ac aa ad)

<强>输入:

aa ab ac ad

<强>输出:

"${ids[@]}"

<强>解释

  • echo - 用于处理shell数组的语法,无论是作为@的一部分还是作为herestring的一部分使用。 tr ' ' '\n'部分表示“数组中的所有元素”
  • sort -u - 将所有空格转换为换行符。因为你的数组被shell看作一行上的元素,用空格分隔;因为sort要求输入在不同的行上。
  • tr '\n' ' ' - 排序并仅保留唯一元素
  • $(...) - 将我们之前添加的换行符转换回空格。
  • tr ' ' '\n' <<< "${ids[@]}" - Command Substitution
  • 除此之外:echo "${ids[@]}" | tr ' ' '\n'是一种更有效的方式:{{1}}

答案 1 :(得分:22)

如果您正在运行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循环到达aaa[2]的第二个值时,它会覆盖最初为b[aa]设置的a[0]

使用本地bash执行操作比使用管道和sortuniq等外部工具更快。

如果您有信心,可以使用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的原因是在分词之前确定了数组值。这意味着命令替换的输出被认为是一个单词而不是一组key = value对。

虽然这使用子shell,但它只使用bash builtins来处理数组值。请务必以批判的眼光评估您对eval的使用情况。如果你不是100%确信chepner或glenn jackman或greycat会发现你的代码没有错误,那就改用for循环。

答案 2 :(得分:11)

如果你的数组元素有空格或任何其他shell特殊字符(并且你能确定它们不是吗?)那么首先捕获它们(你应该总是这样做)用双引号表达你的数组!例如"${a[@]}"。 Bash会将其解释为“单独的参数中的每个数组元素”。在bash中,这总是很有效。

然后,为了获得一个已排序(且唯一)的数组,我们必须将其转换为格式排序,并且能够将其转换回bash数组元素。这是我提出的最好的:

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

不幸的是,这在空数组的特殊情况下失败,将空数组转换为1个空元素的数组(因为printf有0个参数但仍然打印好像它有一个空参数 - 参见解释)。所以你必须在if或者什么东西中捕获它。

说明: printf“shell的%q格式转义”打印的参数,就像bash可以像eval这样恢复! 因为每个元素都是在它自己的行上打印转义的,所以元素之间的唯一分隔符是换行符,数组赋值将每一行作为一个元素,将转义的值解析为文本文本。

e.g。

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

eval是必要的,可以去掉每个返回数组的值。

答案 3 :(得分:8)

我意识到这已经得到了解答,但它在搜索结果中显得非常高,并且可能对某人有所帮助。

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 :(得分:7)

'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` )

答案 5 :(得分:2)

这个也将保留顺序:

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

并使用唯一值修改原始数组:

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

答案 6 :(得分: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) )之类的操作。它会破坏空间。

答案 7 :(得分:1)

  

cat number.txt

1 2 3 4 4 3 2 5 6
  

将行打印到列中: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]++'

4
3
2
  

替换重复记录: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"}

1
5
6

答案 8 :(得分: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

答案 9 :(得分:1)

处理嵌入式空格的另一种方法是,用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)

最后,inputoutput包含所需的值(提供的顺序并不重要):

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

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

答案 10 :(得分:1)

这种变化如何?

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

答案 11 :(得分:0)

不丢失原始订单:

uniques=($(tr ' ' '\n' <<<"${original[@]}" | awk '!u[$0]++' | tr '\n' ' '))

答案 12 :(得分:0)

尝试此操作以获取文件

中第一列的uniq值
OnActionExecuting

答案 13 :(得分:0)

以下所有在 bashsh 中都有效并且在 shellcheck 中没有错误,但您需要取消 SC2207

arrOrig=("192.168.3.4" "192.168.3.4" "192.168.3.3")

# NO SORTING
# shellcheck disable=SC2207
arr1=($(tr ' ' '\n' <<<"${arrOrig[@]}" | awk '!u[$0]++' | tr '\n' ' ')) # @estani
len1=${#arr1[@]}
echo "${len1}"
echo "${arr1[*]}"

# SORTING
# shellcheck disable=SC2207
arr2=($(printf '%s\n' "${arrOrig[@]}" | sort -u)) # @das.cyklone
len2=${#arr2[@]}
echo "${len2}"
echo "${arr2[*]}"

# SORTING
# shellcheck disable=SC2207
arr3=($(echo "${arrOrig[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')) # @sampson-chen
len3=${#arr3[@]}
echo "${len3}"
echo "${arr3[*]}"

# SORTING
# shellcheck disable=SC2207
arr4=($(for i in "${arrOrig[@]}"; do echo "${i}"; done | sort -u)) # @corbyn42
len4=${#arr4[@]}
echo "${len4}"
echo "${arr4[*]}"

# NO SORTING
# shellcheck disable=SC2207
arr5=($(echo "${arrOrig[@]}" | tr "[:space:]" '\n' | awk '!a[$0]++')) # @faustus
len5=${#arr5[@]}
echo "${len5}"
echo "${arr5[*]}"

# OUTPUTS

# arr1
2 # length
192.168.3.4 192.168.3.3 # items

# arr2
2 # length
192.168.3.3 192.168.3.4 # items

# arr3
2 # length
192.168.3.3 192.168.3.4 # items

# arr4
2 # length
192.168.3.3 192.168.3.4 # items

# arr5
2 # length
192.168.3.4 192.168.3.3 # items

所有这些的输出都是 2 并且正确。这个答案基本上总结和整理了这篇文章中的其他答案,是一个有用的快速参考。给出了原始答案的归属。

答案 14 :(得分:-1)

# 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