112

我有几乎和这里一样的问题。

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

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

我究竟做错了什么?

4

16 回答 16

159

有点hacky,但应该这样做:

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

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

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

如果您的 shell 支持herestrings ( bashshould),您可以echo通过将其更改为:

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

截至 2021 年 8 月 28 日的说明:

根据ShellCheck wiki 2207read -a ,应使用管道以避免分裂。因此,在 bash 中,命令将是:

IFS=" " read -r -a ids <<< "$(echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')"

或者

IFS=" " read -r -a ids <<< "$(tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' ')"

输入:

ids=(aa ab aa ac aa ad)

输出:

aa ab ac ad

解释:

  • "${ids[@]}"- 用于处理 shell 数组的语法,无论是用作字符串的一部分echo还是作为字符串的一部分。该@部分表示“数组中的所有元素”
  • tr ' ' '\n'- 将所有空格转换为换行符。因为你的数组被shell看作是一行中的元素,用空格分隔;并且因为 sort 期望输入在不同的行上。
  • sort -u- 仅排序和保留独特的元素
  • tr '\n' ' '- 将我们之前添加的换行符转换回空格。
  • $(...)-命令替换
  • 除了:tr ' ' '\n' <<< "${ids[@]}"是一种更有效的方式:echo "${ids[@]}" | tr ' ' '\n'
于 2012-11-30T15:45:46.560 回答
37

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

在本机 bash 中执行操作可能比使用管道和外部工具(如sortand )更快uniq,但对于较大的数据集,如果您使用更强大的语言(如 awk、python 等),您可能会看到更好的性能。

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

虽然这使用了一个子shell,但它只使用 bash 内置函数来处理数组值。请务必以eval批判的眼光评估您的使用情况。如果您不是 100% 确信 chepner 或 glenn jackman 或 graycat 不会发现您的代码有问题,请改用 for 循环。

于 2012-11-30T16:40:16.390 回答
26

我意识到这已经得到了回答,但它在搜索结果中的显示率很高,它可能会对某人有所帮助。

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
~>
于 2013-07-10T05:12:47.360 回答
15

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

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

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

不幸的是,这在空数组的特殊情况下失败了,将空数组变成了一个包含 1 个空元素的数组(因为 printf 有 0 个参数,但仍然像有一个空参数一样打印 - 请参阅解释)。所以你必须在 if 或其他东西中抓住它。

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

例如

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

eval 是必要的,以去除每个返回数组的值的转义。

于 2013-07-20T04:16:59.170 回答
12

'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` )
于 2015-09-14T15:02:48.247 回答
6

这也将保持秩序:

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

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

ARRAY=($(echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'))
于 2015-06-24T22:51:29.420 回答
6

要创建一个包含唯一值的新数组,请确保您的数组不为空,然后执行以下操作之一:

删除重复条目(带排序)

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) ). 它将打破空间。

于 2015-07-31T02:05:59.120 回答
4

这种变化怎么样?

printf '%s\n' "${ids[@]}" | sort -u
于 2020-08-20T21:03:27.210 回答
3

在不丢失原始顺序的情况下:

uniques=($(tr ' ' '\n' <<<"${original[@]}" | awk '!u[$0]++' | tr '\n' ' '))
于 2015-05-21T15:10:02.050 回答
3

猫号.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
于 2016-10-06T12:54:29.437 回答
3

如果您想要一个仅使用 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
于 2017-01-11T14:42:09.237 回答
2

处理嵌入空格的另一种选择是用 进行空分隔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'
于 2019-09-04T17:14:50.553 回答
1

以下所有工作都在bash并且sh没有错误,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 并且是正确的。这个答案基本上总结和整理了这篇文章中的其他答案,是一个有用的快速参考。给出了原始答案的归属。

于 2021-05-20T16:36:54.557 回答
1

在 zsh 中,您可以使用 (u) 标志:

$ ids=(aa ab aa ac aa ad)
$ print ${(u)ids}
aa ab ac ad
于 2021-11-28T21:33:16.567 回答
0

试试这个来获取文件中第一列的 uniq 值

awk -F, '{a[$1];}END{for (i in a)print i;}'
于 2016-10-24T09:10:38.840 回答
-2
# 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
于 2019-04-12T15:22:53.993 回答