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

Jet*_*tse 75 linux arrays bash unique

我和这里的问题差不多.

我有一个包含aa ab aa ac aa ad等的数组.现在我想从这个数组中选择所有独特的元素.认为,这将是简单的用sort | uniqsort -u因为他们在其他问题中提到,但没有在数组中改变...的代码是:

echo `echo "${ids[@]}" | sort | uniq`
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

sam*_*hen 111

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

echo "${ids[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' '
Run Code Online (Sandbox Code Playgroud)

要将排序的唯一结果保存回数组,请执行数组赋值:

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

如果你的shell支持herestrings(bash应该),你可以echo通过将其改为:

tr ' ' '\n' <<< "${ids[@]}" | sort -u | tr '\n' ' '
Run Code Online (Sandbox Code Playgroud)

输入:

ids=(aa ab aa ac aa ad)
Run Code Online (Sandbox Code Playgroud)

输出:

aa ab ac ad
Run Code Online (Sandbox Code Playgroud)

说明:

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

  • +1.有点整洁:将uniq元素存储在一个新数组中:`uniq =($(printf"%s \n""$ {ids [@]}"| sort -u)); echo"$ {uniq [@]}"` (26认同)
  • +1我不确定这是否是一个孤立的案例,但是将唯一的项目放回到数组中需要额外的括号,例如:`sorted_unique_ids =($(echo"$ {ids [@]}"| tr'''\n'| sort -u | tr'\n'''))`.没有额外的括号,它将其作为字符串给出. (4认同)
  • -1:这会将包含空格的数组元素分解为多个值,这(对我来说)是使用数组而不是简单的空格分隔字符串的主要好处之一。 (3认同)
  • 如果您不想改变元素的顺序,请使用`... | uniq | ......`而不是`... | sort -u | ...`. (2认同)
  • @ Jesse,`uniq`仅删除_consecutive_重复项。在此答案的示例中,sorted_unique_ids最终将与原始ids相同。要保留顺序,请尝试`... | awk'!seen [$ 0] ++'`。另请参阅/sf/ask/101108451/。 (2认同)

gho*_*oti 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
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为在数组中,每个键只能出现一次.当for循环到达aain 的第二个值时a[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" )
Run Code Online (Sandbox Code Playgroud)

此解决方案需要的原因eval是在分词之前确定了数组值.这意味着命令替换的输出被认为是单个字而不是一组键=值对.

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

  • @Benubird - 你可以粘贴你的终端内容吗?它对我来说非常适合,所以我最好的猜测是你有(1)一个拼写错误,(2)旧版本的bash(关联数组被添加到v4),或(3)宇宙背景大量涌入由邻居地下室的量子黑洞引起的辐射,对您计算机内的信号产生干扰。 (2认同)

das*_*one 12

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

printf "%s\n" "${IDS[@]}" | sort -u
Run Code Online (Sandbox Code Playgroud)

例:

~> 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
~>
Run Code Online (Sandbox Code Playgroud)


von*_*app 11

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

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

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

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

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

例如

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

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

  • 请注意,“uniq”在未排序的列表上无法正常工作,因此它必须始终与“sort”结合使用。 (2认同)

Six*_*Six 9

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

删除重复条目(带排序)

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

删除重复条目(不排序)

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

警告:不要尝试做类似的事情NewArray=( $(printf '%s\n' "${OriginalArray[@]}" | sort -u) )。它会在空格上破裂。


小智 8

'sort'可用于命令for循环的输出:

for i in ${ids[@]}; do echo $i; done | sort
Run Code Online (Sandbox Code Playgroud)

并使用"-u"消除重复:

for i in ${ids[@]}; do echo $i; done | sort -u
Run Code Online (Sandbox Code Playgroud)

最后,您可以使用唯一元素覆盖您的数组:

ids=( `for i in ${ids[@]}; do echo $i; done | sort -u` )
Run Code Online (Sandbox Code Playgroud)

  • 但请注意,如果不更改顺序,您也不会获得所需的结果,因为“uniq”仅删除“相邻”重复行。 (2认同)

Joh*_*ese 7

这个变化怎么样?

printf '%s\n' "${ids[@]}" | sort -u
Run Code Online (Sandbox Code Playgroud)


fau*_*tus 6

这也将保持秩序:

echo ${ARRAY[@]} | tr [:space:] '\n' | awk '!a[$0]++'
Run Code Online (Sandbox Code Playgroud)

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

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


est*_*ani 5

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

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


VIP*_*MAR 5

猫号.txt

1 2 3 4 4 3 2 5 6
Run Code Online (Sandbox Code Playgroud)

将行打印到列中:cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}'

1
2
3
4
4
3
2
5
6
Run Code Online (Sandbox Code Playgroud)

查找重复记录:cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk 'x[$0]++'

4
3
2
Run Code Online (Sandbox Code Playgroud)

替换重复记录:cat number.txt | awk '{for(i=1;i<=NF;i++) print $i}' |awk '!x[$0]++'

1
2
3
4
5
6
Run Code Online (Sandbox Code Playgroud)

仅查找 Uniq 记录: cat number.txt | awk '{for(i=1;i<=NF;i++) print $i|"sort|uniq -u"}

1
5
6
Run Code Online (Sandbox Code Playgroud)


rln*_*rln 5

如果您想要一个仅使用 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
Run Code Online (Sandbox Code Playgroud)

这将输出

bar
foo
bar none
Run Code Online (Sandbox Code Playgroud)