【问题标题】:How to remove duplicated characters from string in Bash?如何从 Bash 中的字符串中删除重复字符?
【发布时间】:2014-05-01 05:52:17
【问题描述】:

我有一个字符串

cabbagee 

我想删除重复的字符。 如果我使用 tr -s 它将删除序列中的重复字符。但我想要的输出是

cabge

如果有人可以帮助我,不胜感激。

提供的答案是正确的,但我无法使用 awk,所以我使用了:

#!/usr/bin/bash
key=$1
len=${#key}
mkey=""
for (( c=0; c<len; c++ ))
do
    tmp=${key:$c:1}
    echo $mkey | grep $tmp >/dev/null 2>&1   
    if [ "$?" -eq "0" ]; then
        echo "Found $tmp in $mkey"
    else
        mkey+=$tmp
    fi
done
echo $mkey

【问题讨论】:

    标签: regex linux bash tr


    【解决方案1】:

    你能用awk吗?

    awk -v FS="" '{
        for(i=1;i<=NF;i++)str=(++a[$i]==1?str $i:str)
    }
    END {print str}' <<< "cabbagee"
    cabge
    

    其他几种方式:

    gnuawk:

    awk -v RS='[a-z]' '{str=(++a[RT]==1?str RT: str)}END{print str}' <<< "cabbagee"
    cabge
    

    awk -v RS='[a-z]' -v ORS= '++a[RT]==1{print RT}END{print "\n"}' <<< "cabbagee"
    cabge
    

    gnu sedawk

    sed 's/./&\n/g' <<< "cabbagee" | awk '!a[$1]++' | sed ':a;N;s/\n//;ba'
    cabge
    

    【讨论】:

    • 感谢您的回答。你能解释一下吗?
    • @Bernard 当然,我们将字段分隔符 (FS) 设置为空字符串,以便将整个单词拆分为字符。我们遍历单词中的每个字符并检查该字符是否只出现在数组中一次(以删除重复)。我们将单次出现的字符填充到字符串变量 (str)。在END 块中,我们打印字符串变量。
    • 好一个!我想知道是否可以设置RS 使每个字符都是一条记录,然后您可以使用{! a[$0]++} 技巧。
    • 看来这是作业。请参阅链接的问题。此外,我记得不久前在 Python 标记中也有人问过这个问题:)
    • 谢谢@fedorqui。我使用您提到的技巧添加了另外几种方法。虽然额外的管道可能会让少数人关闭。 :)
    【解决方案2】:

    您编辑了您的帖子并发布了一个丑陋且破碎的答案。在纯 Bash 中更简单、更有效、更有效:

    #!/bin/bash
    
    key=$1
    mkey=$key
    for ((i=0;i<${#mkey};++i)); do
        c=${mkey:i:1}
        tailmkey=${mkey:i+1}
        mkey=${mkey::i+1}${tailmkey//"$c"/}
    done
    echo "$mkey"
    

    为什么你的脚本坏了?以下是一些你的失败而我的失败的情况。为了演示,我调用了你的脚本banana 和我的gorilla。哦,因为我不是故意的,所以我修复了您的脚本存在的琐碎引用问题(与* 字符无关)并评论了泛滥的部分:

    #!/usr/bin/bash
    key=$1
    len=${#key}
    mkey=""
    for (( c=0; c<len; c++ )); do
        tmp=${key:$c:1}
        echo "$mkey" | grep "$tmp" >/dev/null 2>&1   # Added quotes here!
        if [ "$?" -eq "0" ]; then
            : # echo "Found $tmp in $mkey" # Commented this to remove flooding
        else
            mkey+=$tmp
        fi
    done
    echo "$mkey"   # Added quotes here!
    

    那么我们走吧:

    $ ./banana '^'
    
    $ ./gorilla '^'
    '^'
    

    是的,那是因为^ 是 grep 的正则表达式中使用的字符。 $. 也会发生类似的事情:

    $ ./banana 'a.'
    a
    $ ./gorilla 'a.'
    a.
    

    现在反斜杠也会导致问题:

    $ ./banana '\\'
    \\
    $ ./gorilla '\\'
    \
    

    (删除&gt;/dev/null 2&gt;&amp;1 部分以查看grep: Trailing backslash 错误)。 [ 也会发生同样的事情。

    更不用说您的脚本效率极低!它多次调用grep。在这方面我的要好一点:

    $ time for i in {1..200}; do ./banana cabbage; done &>/dev/null
    
    real    0m3.028s
    user    0m0.216s
    sys     0m0.464s
    $ time for i in {1..200}; do ./gorilla cabbage; done &>/dev/null
    
    real    0m0.878s
    user    0m0.172s
    sys     0m0.324s
    

    还不错吧?

    另一个不言自明的基准:用长字符串,例如,Lorem Ipsum 的一段:

    $ time ./banana 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.'
    Lorem ipsudlta,cngDSMqvhPbNAUfCI
    
    real    0m1.464s
    user    0m0.104s
    sys     0m0.224s
    $ time ./gorilla 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.'
    Lorem ipsudlta,cng.DSMqvhPbNAUfCI
    
    real    0m0.013s
    user    0m0.000s
    sys     0m0.008s
    

    这是因为banana 为输入字符串的每个字符调用grep,而gorilla 动态执行删除。 (我不会提到banana 错过了这段时间)。

    【讨论】:

    • 'tailmkey//"$c"/' 是什么?看不懂?
    • @Bernard 试试这个:var=banana; echo "${var//a/o}"。这将输出var 的扩展,但所有出现的a 都被o 替换——在本例中为bonono。所以${tailmkey//"$c"/} 扩展为tailmkey,所有出现的$c 扩展为替换为nothing。见the manual
    【解决方案3】:

    怎么样:

    echo "cabbagee" | sed 's/./&\n/g' | perl -ne '$H{$_}++ or print' | tr -d '\n'
    

    产量:

    cabge
    

    上面将字符串的字符分成单独的行(sed 's/./&amp;\n/g'),然后使用一点perl 魔法(信用unix tool to remove duplicate lines from a file)删除任何重复的行。最后,tr -d '\n' 删除了我们添加的换行符以实现您想要的输出。

    可能需要根据您的特定目的对其进行一些修改,感觉非常hacky,但似乎可以完成工作。

    祝你好运。

    【讨论】:

    • 嗯...看起来换行符不在您的末尾。就我而言,输出是cabage,这仍然不是您想要的。抱歉,uniq 只会拉出相邻的重复项,例如 tr -s。等一下,我看看我能不能想出一个解决办法。
    • 如果不对行进行排序,这将相当棘手……我猜它需要使用awkperl 或类似的方法进行一些解析。
    • 我正在创建两个循环,但还是失败了,因为我不太擅长 shell
    • @Bernard...尝试最新的编辑。我从stackoverflow.com/questions/746689/… 中获取了一些perl 魔法,完成了工作。
    • 我不知道为什么,但我的输出是:cnanbnbnangnenen
    【解决方案4】:

    您可以使用grep -o . 将每个字符与\n 分开,然后只收集未在bash 中看到的字符:

    grep -o . <<<'cabbagee' | \
    { while read c; do [[ "$s" = *$c* ]] || s=$s$c; done; echo $s; }
    

    【讨论】:

      【解决方案5】:

      我不确定您使用哪种语言执行此操作,但您始终可以创建一个 for 循环来遍历字符串。然后进行 if 循环说明 if yourstring.charAt(i).equals(yourstring.char(i+1){ replace(yourstring.char(i+1),"")} 所以基本上是通过一个循环来说明当前索引处的字符是否等于下一个索引处的字符,然后用空字符串替换下一个索引:“”。

      【讨论】:

      • 我知道,但我正在使用 bash 脚本。
      • @Ducodenator 你应该在你的答案中放一个例子,因为它会让你的答案更容易阅读和更有帮助。
      猜你喜欢
      • 2021-04-17
      • 1970-01-01
      • 1970-01-01
      • 2016-10-24
      • 1970-01-01
      • 1970-01-01
      • 2012-04-08
      • 1970-01-01
      相关资源
      最近更新 更多