在不排序的情况下去除变量中的重复项

7
我有一个变量,其中包含以下以空格分隔的条目。
variable="apple lemon papaya avocado lemon grapes papaya apple avocado mango banana"

如何在不排序的情况下删除重复项?
#Something like this.
new_variable="apple lemon papaya avocado grapes mango banana"

我找到了一个脚本,可以去除变量中的重复内容,但不会排序。
#Not something like this.
new_variable=$(echo "$variable"|tr " " "\n"|sort|uniq|tr "\n" " ")
echo $new_variable
apple avocado banana grapes lemon mango papaya
8个回答

22
new_variable=$( awk 'BEGIN{RS=ORS=" "}!a[$0]++' <<<$variable );

这是它的工作原理:

RS(输入记录分隔符)设置为空格,使其将$variable中的每个水果视为记录而不是字段。非排序独特性是通过!a [$0] ++实现的。由于awk支持关联数组,它使用当前记录($0)作为数组a []的键。如果此键以前没有被看到过,则a [$0]评估为“0”(未设置索引的awk的默认值),然后取反以返回TRUE。接下来,我利用awk将默认为“print $0”的事实,如果表达式返回TRUE且没有给出' { commands }'。最后,a [$0]被递增,使得这个键不能再返回TRUE,因此重复的值永远不会被打印。ORS(输出记录分隔符)也设置为空格,以模仿输入格式。

产生相同输出的这个命令的语法较少的版本如下:

awk 'BEGIN{RS=ORS=" "}{ if (a[$0] == 0){ a[$0] += 1; print $0}}'

太喜欢awk了 =)

编辑

如果你需要在纯Bash 2.1+中执行此操作,我建议使用以下方法:

#!/bin/bash    

variable="apple lemon papaya avocado lemon grapes papaya apple avocado mango banana"
temp="$variable"

new_variable="${temp%% *}"

while [[ "$temp" != ${new_variable##* } ]]; do
   temp=${temp//${temp%% *} /}
   new_variable="$new_variable ${temp%% *}"
done

echo $new_variable;

1
简单地测试成员资格比计数更好:awk 'BEGIN{RS=ORS=" "} { if (!($0 in a)) { a[$0]; print } }'或者更简洁:awk 'BEGIN{RS=ORS=" "} !($0 in a || a[$0])' - Mark Edgar
@Mark:在一个10,000次迭代的循环中进行“时间”测试显示,你的速度只比我的慢了3%左右。虽然不是很多,但仍然不够好。随着元素数量的增加,这种差异只会变得更大,因为你的版本需要O(n)的时间,而我的始终是恒定的O(1)。 - SiegeX
非常好的解决方案,谢谢。但是如果重复项在末尾连续出现,则无法正常工作。例如:variable="apple lemon papaya papaya",将打印:apple lemon papaya papaya。而如果我有:variable="apple lemon papaya papaya mango",则会删除重复的papaya并打印:apple lemon papaya mango。你有什么想法? - Gregg

6

这个管道版本的工作原理是保留原始顺序:

variable=$(echo "$variable" | tr ' ' '\n' | nl | sort -u -k2 | sort -n | cut -f2-)

这是我唯一有效的解决方案。awk 解决方案仍然存在重复项。谢谢。 - MiloDC

3
纯Bash:
variable="apple lemon papaya avocado lemon grapes papaya apple avocado mango banana"

declare new_value=''

for item in $variable; do
  if [[ ! $new_value =~ $item ]] ; then   # first time?
    new_value="$new_value $item"
  fi
done
new_value=${new_value:1}                  # remove leading blank

好的解决方案,但请注意,由于'=~'运算符,这将限制您在Bash 3.X中使用。 - SiegeX

3

在纯净、可移植的sh中:

words="苹果 柠檬 番木瓜 鳄梨 柠檬 葡萄 番木瓜 苹果 鳄梨 芒果 香蕉"
seen=
for word in $words; do
  case $seen in
    $word\ * | *\ $word | *\ $word\ * | $word) 
      # 已经出现过
      ;;
    *)
      seen="$seen $word"
      ;;
  esac
done
echo $seen

1

shell

declare -a arr
variable="apple lemon papaya avocado lemon grapes papaya apple avocado mango banana"
set -- $variable
count=0
for c in $@
do
    flag=0
    for((i=0;i<=${#arr[@]}-1;i++))
    do
        if [ "${arr[$i]}" == "$c" ] ;then
            flag=1
            break
        fi
    done
    if  [ "$flag" -eq 0 ] ; then
        arr[$count]="$c"
        count=$((count+1))
    fi
done
for((i=0;i<=${#arr[@]}-1;i++))
do
   echo "result: ${arr[$i]}"
done

运行时的结果:

linux# ./myscript.sh
result: apple
result: lemon
result: papaya
result: avocado
result: grapes
result: mango
result: banana

或者如果你想使用 gawk

awk 'BEGIN{RS=ORS=" "} (!($0 in a) ){a[$0];print}'

1

Z Shell:

% variable="apple lemon papaya avocado lemon grapes papaya apple avocado mango banana"
% print ${(zu)variable}                                                               
apple lemon papaya avocado grapes mango banana

0

Perl解决方案:

perl -le 'for (@ARGV){ $h{$_}++ }; for (keys %h){ print $_ }' $variable

@ARGV是从$variable输入参数的列表
遍历列表,使用循环变量$_填充h哈希表
遍历h哈希表的键,并打印每个键

grapes
avocado
apple
lemon
banana
mango
papaya

这个变量首先按频率排序输出 $h{$a} <=> $h{$b},然后按字母顺序排列 $a cmp $b

perl -le 'for (@ARGV){ $h{$_}++ }; for (sort { $h{$a} <=> $h{$b} || $a cmp $b } keys %h){ print "$h{$_}\t$_" }' $variable

1       banana
1       grapes
1       mango
2       apple
2       avocado
2       lemon
2       papaya

这个变体产生与上一个相同的输出。
但是,它不使用输入shell变量,而是使用一个名为“fruits”的输入文件,每行一个水果:

perl -lne '$h{$_}++; END{ for (sort { $h{$a} <=> $h{$b} || $a cmp $b } keys %h){ print "$h{$_}\t$_" } }' fruits


0
另一个 awk 解决方案:
#!/bin/bash
variable="apple lemon papaya avocado lemon grapes papaya apple avocado mango banana"
variable=$(printf '%s\n' "$variable" | awk -v RS='[[:space:]]+' '!a[$0]++{printf "%s%s", $0, RT}')
variable="${variable%,*}"
echo "$variable"

输出:

apple lemon papaya avocado grapes mango banana

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接