【问题标题】:How can I display unique words contained in a Bash string?如何显示 Bash 字符串中包含的唯一单词?
【发布时间】:2016-05-14 18:06:23
【问题描述】:

我有一个包含重复单词的字符串。我只想显示唯一的单词。字符串是:

variable="alpha bravo charlie alpha delta echo charlie"

我知道有几种工具可以一起完成这项工作。这是我想出来的:

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

有什么更有效的方法来做到这一点?

【问题讨论】:

  • 不,这看起来很正常。
  • 您应该澄清您是希望输出是排序唯一词(如在您自己的解决方案尝试中),还是唯一词按输入顺序 .
  • 我已对您的问题进行了轻微编辑,以删除对工具的请求,该请求可能会获得接近投票。经编辑,我认为这是一个合理的问题,许多没有经验的 shell 用户会为之苦恼。

标签: linux string bash duplicates


【解决方案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

【讨论】:

  • 谢谢,这是一个很好的观点。我已经编辑以照顾两者。请检查最后一个 for 循环以按原始顺序打印结果。
  • 很好的答案仍在学习这样的数组。一个漂亮的解决方案。
【解决方案2】:

您可以使用xargs:

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

【讨论】:

  • 虽然这编写起来更简单,但它也比 OP 自己的解决方案效率低,因为它调用了 echo 实用程序(两次) 对于每个输入词。解释xargs 的行为会很有帮助。
【解决方案3】:

你可以使用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

【讨论】:

    【解决方案4】:

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

    一个简单的Awk-only 解决方案(符合POSIX),通过避免管道(总是涉及子shell)而高效

    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,特别是路径名扩展(通配符),以及它如何通过here-string 提供给Awk (&lt;&lt;&lt;)。

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

      • 请注意,last 单词将包含输入行的尾随换行符,这就是为什么我们不使用 $0 - 整个记录 - 但 $1,记录的第一个字段,由于 Awk 的默认字段拆分行为,它已去除换行符。
    • seen[$1]++ 是一个常见的 Awk 习惯用法,它可以为关联数组 seen 中的输入词 $1 创建一个条目(如果它尚不存在),或者增加其出现次数。

    • !seen[$0]++ 因此仅在给定单词的第一次 出现时返回 true(其中 seen[$0] 隐式为零/空字符串;++ 是一个 post-increment,因此直到 条件被评估后才会生效)

    • {printf "%s%s",sep,$1; sep=" "} 打印手头的单词$1,前面是分隔符sep,这隐含地是first 单词的empty 字符串,但后面的单词有一个空格,因为紧接着将sep 设置为" "


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

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

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

    【讨论】:

      【解决方案5】:

      使用 Bash 替换扩展

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

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

      这具有对单词进行排序的副作用,因为 sortuniq 实用程序都需要对输入进行排序以检测重复项。如果这不是你想要的,我还发布了一个Ruby solution,它保留了原始词序。

      重新加入单词

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

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

      命令替换周围没有引号是故意的。如果您引用它,则将保留换行符,因为 Bash 不会执行 word-splitting。如果不加引号,shell 会将结果作为单行返回,无论这看起来多么不直观。

      【讨论】:

      • @mklement0 我采用了不同的方法,因为这里的字符串通过管道传输到 sort 和 tr 仍然会产生两个进程。但是,我认为您的此处字符串解决方案很有趣,您应该将其发布为答案。感谢您的周到!
      • 谢谢,@CodeGnome。我在这里用我提出的替代方案重新创建我之前的评论(其中一部分不再适用)(不认为值得作为单独的答案发布):sort -u &lt;&lt;&lt;"${variable// /$'\n'}" | tr '\n' ' '。正如您所说,这会产生 2 个进程。您的等效命令(带有重新加入的单词)会产生 3 个进程,并且还会使生成的单词受到路径名扩展(通配符)的影响。
      • 这对我来说是新的,bash 替换可以处理换行符。谢谢你。只需要使用一个内置插件可以提高我的 bash 熟练度,而且由于我可能会使用它,因此可以节省一些时间。
      【解决方案6】:

      使用 Ruby One-Liner 保留输入顺序

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

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

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

      sortuniq 实用程序不同,Ruby 不需要对单词进行排序来检测重复项。如果您不希望对结果进行排序,这可能是一个更好的解决方案,尽管考虑到您的输入样本,它对发布的示例没有实际影响。

      重新加入单词

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

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

      【讨论】:

        【解决方案7】:

        纯粹、丑陋的 bash:

        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
        

        【讨论】:

        • 子shell 的使用降低了效率,通常需要注意eval。以这种方式使用for 会使单词受到路径名扩展的影响。这是解决所有这些问题的变体;通过使用变量间接避免evalread -ra words &lt;&lt;&lt;"$variable"; for w in "${words[@]}"; do vn="un_$w"; if [[ -z ${!vn} ]]; then echo -n "$w "; declare "$vn=1"; fi; done
        猜你喜欢
        • 2021-09-13
        • 1970-01-01
        • 2015-11-22
        • 1970-01-01
        • 2012-05-20
        • 2014-05-20
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多