Как удалить повторяющиеся символы из строки в Bash?

у меня есть строка

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

person Bernard    schedule 01.05.2014    source источник


Ответы (5)


Вы отредактировали свой пост и опубликовали ответ, который уродлив и сломан. Более простой, работающий и более эффективный, в чистом 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 '\\'
\

(удалите часть >/dev/null 2>&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 пропустил точку).

person gniourf_gniourf    schedule 01.05.2014
comment
Что такое 'tailmkey//$c/'? Не могу понять? - person Bernard; 02.05.2014
comment
@Bernard Попробуйте это: var=banana; echo "${var//a/o}". Это выводит расширение var, но с заменой всех вхождений a на o — в данном случае bonono. Таким образом, ${tailmkey//"$c"/} заменяется на tailmkey со всеми вхождениями того, что $c заменяется на ничего. См. руководство. - person gniourf_gniourf; 02.05.2014

Как насчет:

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

Который дает:

cabge

Приведенное выше разбивает символы вашей строки на отдельные строки (sed 's/./&\n/g'), а затем использует немного магии perl (кредит инструмент unix для удаления повторяющихся строк из файла), чтобы удалить любые повторяющиеся строки. Наконец, tr -d '\n' удаляет новые строки, которые мы добавили для достижения желаемого результата.

Возможно, вам придется немного изменить его для вашей конкретной цели, и он кажется ужасно хакерским, но, похоже, он выполняет свою работу.

Удачи.

person daBeamer    schedule 01.05.2014
comment
Хммм... похоже, символа новой строки нет на вашем конце. С моей стороны, вывод cabage, что все еще не то, что вы ищете. Извините, uniq вытаскивает только соседние дубликаты а-ля tr -s. Секунду, я посмотрю, смогу ли я придумать исправление. - person daBeamer; 01.05.2014
comment
Это довольно сложно без сортировки строк ... Я предполагаю, что потребуется некоторая магия синтаксического анализа с использованием awk, perl или подобных. - person daBeamer; 01.05.2014
comment
Я работаю над созданием двух циклов, но все же мне это не удалось, потому что я не так хорош в оболочке. - person Bernard; 01.05.2014
comment
@Bernard... попробуйте последнее редактирование. Я взял немного магии perl из stackoverflow.com/questions/746689/, который выполняет свою работу. - person daBeamer; 01.05.2014
comment
Я не знаю почему, но мой вывод: cnanbnbnangnenen - person Bernard; 01.05.2014
comment
@Bernard Да, не связывайтесь с моим, используйте ответ JS웃, который использует awk. Гораздо более чистое решение. Я был ленив. Если вы хотите решить проблему для собственного здравомыслия, я предполагаю, что в вашей команде sed отсутствует символ новой строки (часть \n, вероятно, просто n). - person daBeamer; 01.05.2014

Вы можете использовать grep -o ., чтобы разделить каждый символ с помощью \n, а затем собрать только символы, которые не были замечены в bash:

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

Я не уверен, на каком языке вы это делаете, но вы всегда можете сделать цикл for для прохождения строки. Затем создайте цикл if, указав, что если yourstring.charAt(i).equals(yourstring.char(i+1){ replace(yourstring.char(i+1),"")}). по текущему индексу равен символу по следующему индексу, то замените следующий индекс пустой строкой: "".

person Ducodenator    schedule 01.05.2014
comment
Я знаю это, но я использую скрипт bash. - person Bernard; 01.05.2014
comment
@Ducodenator Вы должны привести пример в свой ответ, так как это сделает ваш ответ более легким для чтения и более полезным. - person Tyler Carter; 02.05.2014

person    schedule
comment
Спасибо за Ваш ответ. Можете ли вы немного объяснить? - person Bernard; 01.05.2014
comment
@Bernard Конечно, мы установили разделитель полей (FS) в нулевую строку, чтобы он разделил все ваше слово на символы. Мы перебираем каждый символ в слове и проверяем, присутствует ли символ в массиве только один раз (чтобы удалить дубликаты). Мы добавляем единственное вхождение символа в строковую переменную (str). В блоке END печатаем строковую переменную. - person jaypal singh; 01.05.2014
comment
Хороший! Мне интересно, можно ли установить RS так, чтобы каждый символ был записью, а затем вы можете использовать трюк {! a[$0]++}. - person fedorqui 'SO stop harming'; 01.05.2014
comment
Кажется, это было домашнее задание. Обратитесь к связанному вопросу. Более того, я помню, что это тоже спрашивали в теге Python некоторое время назад :) - person devnull; 01.05.2014
comment
Спасибо @fedorqui. Я добавил еще пару способов, используя упомянутый вами трюк. Хотя дополнительные трубы могут быть отворотом для немногих. :) - person jaypal singh; 01.05.2014