如何显示Bash字符串中包含的唯一单词?

时间:2016-02-04 22:14:34

标签: linux string bash duplicates

我有一个重复单词的字符串。我想只显示独特的单词。字符串是:

variable="alpha bravo charlie alpha delta echo charlie"

我知道有几种工具可以一起做到这一点。这就是我想到的:

echo $variable | tr " " "\n" | sort -u | tr "\n" " "

有什么更有效的方法呢?

7 个答案:

答案 0 :(得分:3)

您可以使用xargs

echo "$variable" | xargs -n 1 | sort -u | xargs

答案 1 :(得分:2)

注意:此解决方案假定所有唯一字应按照输入中遇到的顺序输出。相比之下,OP自己的解决方案尝试输出排序的唯一字词列表。

一个简单的仅限Awk的解决方案(符合POSIX标准),它通过避免管道(总是涉及子单元)来高效

awk -v RS=' ' '{ if (!seen[$1]++) { printf "%s%s",sep,$1; sep=" " } }' <<<"$variable"

# The above prints without a trailing \n, as in the OP's own solution.
# To add a trailing newline, append  `END { print }` to the end 
# of the Awk script.
  • 请注意$variable如何双引号以防止意外shell expansions,特别是路径名扩展(globbing),以及如何通过它提供给Awk here-string<<<)。

  • -v RS=' '告诉Awk将输入分成记录一个空格。

    • 请注意, last 字会包含输入行的尾随换行符,这就是我们不使用$0 - 整个记录的原因 - 但{ {1}},记录的第一个字段,由于Awk的默认字段拆分行为而删除了换行符。
  • $1是一种常见的Awk习惯用法,如果它尚不存在,则会在关联数组seen[$1]++中为输入字$1创建一个条目,或增加其发生次数。

  • seen因此仅对给定单词的第一个出现返回true(其中!seen[$0]++隐式为零/空字符串; {{1} }是一个帖子 - 增量,因此在 评估条件之后才生效

  • seen[$0]打印手边的单词++,前面是分隔符{printf "%s%s",sep,$1; sep=" "},它隐式地是字符串单词,但是后续单词的单个空格,因为在$1之后立即将sep设置为sep

这是一个更灵活的变体,处理输入词之间的任何空格;它适用于 GNU Awk和 Mawk [1]

" "
  • awk -v RS='[[:space:]]+' '{if (!seen[$0]++){printf "%s%s",sep,$0; sep=" "}}' <<<"$variable" 告诉Awk通过空格,制表符和换行符的任意组合将输入拆分为记录。

[1]不幸的是,BSD / OSX Awk(严格遵守POSIX spec),不支持使用正则表达式甚至多字符文字作为-v RS='[[:space:]]s+',输入记录分隔符。

答案 2 :(得分:2)

使用Bash替换扩展

以下shell parameter expansion将使用换行符替换空格,然后将结果传递到 sort 实用程序,以仅返回唯一的单词。

$ echo -e "${variable// /\\n}" | sort -u
alpha
bravo
charlie
delta
echo

这会产生排序单词的副作用,因为 sort uniq 实用程序都需要对输入进行排序以检测重复项。如果那不是你想要的,我还发布了Ruby solution,保留了原始的单词顺序。

重新加入单词

如果像一位评论者所指出的那样,您正在尝试将您的独特单词重新组合成一行,则可以使用command substitution来执行此操作。例如:

$ echo $(echo -e "${variable// /\\n}" | sort -u)
alpha bravo charlie delta echo

命令替换缺乏引号是故意的。如果你引用它,新行将被保留,因为Bash不会word-splitting。不带引号,shell会将结果作为单行返回,但看起来不直观。

答案 3 :(得分:2)

使用Ruby One-Liner

保留输入顺序

我已经发布了Bash-specific answer,但如果你想在保留原始字符串的单词顺序时只返回唯一的单词,那么你可以使用以下的Ruby单行代码:

$ echo "$variable" | ruby -ne 'puts $_.split.uniq'
alpha
bravo
charlie
delta
echo

这会将输入字符串拆分为空格,然后从结果数组中返回唯一元素。

sort uniq 实用程序不同,Ruby不需要对单词进行排序以检测重复项。如果您不希望对结果进行排序,这可能是一个更好的解决方案,尽管根据您的输入示例,它对发布的示例没有实际区别。

重新加入单词

如果像一位评论者指出的那样,你在重复数据删除后尝试将这些单词重新组合成一行,那么你也可以这样做。为此,我们只需附加Array#join方法:

$ echo "$variable" | ruby -ne 'puts $_.split.uniq.join(" ")'
alpha bravo charlie delta echo

答案 4 :(得分:1)

使用BASH 4+中的关联数组,您可以简化:

variable="alpha bravo charlie alpha delta echo charlie"

# declare an associative array
declare -A unq

# read sentence into an indexed array
read -ra arr <<< "$variable"

# iterate each word and populate associative array with word as key
for w in "${arr[@]}"; do
   unq["$w"]=1
done

# print unique results
printf "%s\n" "${!unq[@]}"
delta
bravo
echo
alpha
charlie

## if you want results in same order as original string
for w in "${arr[@]}"; do
   [[ ${unq["$w"]} ]] && echo "$w" && unset unq["$w"]
done
alpha
bravo
charlie
delta
echo

答案 5 :(得分:1)

您可以使用awk:

$ echo "$variable" | awk  '{for(i=1;i<=NF;i++){if (!seen[$i]++) printf $i" "}}'
alpha bravo charlie delta echo 

如果您不想要尾随空格并想要一个尾随CR,您可以这样做:

$ echo "$variable" | awk  'BEGIN{j=""} {for(i=1;i<=NF;i++){if (!seen[$i]++)j=j==""?j=$i:j=j" "$i}} END{print j}' 
alpha bravo charlie delta echo

答案 6 :(得分:-1)

纯粹,丑陋的狂欢:

for x in $vaviable; do 
    if [ "$(eval echo $(echo \$un__$x))" = "" ]; then
         echo -n $x
         eval un__$x=1
         __usv="$__usv un__$x"
    fi
done
unset $__usv