Как я могу отображать уникальные слова, содержащиеся в строке Bash?

у меня есть строка, которая содержит повторяющиеся слова. Я хотел бы показать только уникальные слова. Строка:

variable="alpha bravo charlie alpha delta echo charlie"

Я знаю несколько инструментов, которые могут сделать это вместе. Вот что я понял:--3-->

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

что является более эффективным способом сделать это?

7 ответов


используйте расширение подстановки Bash

следующее расширение параметров оболочки заменит пробелы новыми строками, а затем передаст результаты в вроде утилита для возврата только уникальных слов.

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

это имеет побочный эффект сортировки ваших слов, как вроде и uniq утилиты оба требуют ввода для сортировки, чтобы обнаружить дубликаты. Если это не то, что вы хотите, я также выложил Руби решением это сохраняет исходный порядок слов.

Забаненный Слова

если, как указал один комментатор, вы пытаетесь собрать свои уникальные слова обратно в одну строку, вы можете использовать команду для этого. Например:

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

отсутствие кавычек вокруг подстановки команд намеренно. Если вы цитируете его, новые строки будут сохранены, потому что Bash не будет делать слово-расщепление. Без кавычек оболочка вернет результаты в виде одной строки, каким бы неинтуитивным это ни казалось.


Вы можете использовать xargs:

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

сохранить порядок ввода с помощью Ruby One-Liner

Я написал Bash-конкретный ответ уже, но если вы хотите вернуть только уникальные слова при сохранении порядка слов исходной строки, то вы можете использовать следующий Ruby One-liner:

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

это разделит входную строку на пробелы, а затем вернет уникальные элементы из результирующего массива.

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

Забаненный Слова

если, как указал один комментатор, вы пытаетесь собрать слова обратно в одну строку после дедупликации, вы также можете это сделать. Для этого мы просто добавляем массив#join метод:

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

Примечание: это решение предполагает, что все уникальные слова должны быть выведены в том порядке, в котором они встречаются на входе. Напротив, собственная попытка решения OP выводит отсортированный список уникальных слов.

простой Awk-только решение (POSIX-совместимый) это эффективное избегая конвейера (который неизменно включает подсхемы).

awk -v RS=' ' '{ if (!seen[]++) { printf "%s%s",sep,; 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 и двойные кавычки чтобы предотвратить его от случайного оболочки расширения, в частности расширение пути (globbing), и как он предоставляется Awk через строка (<<<).

  • -v RS=' ' говорит Awk разделить вход на записи один пробел.

    • отметим, что последние word будет иметь конечную новую строку входной линии, поэтому мы не использовать - вся запись-но , первое поле записи, в котором новая строка удалена из-за поведения разделения полей Awk по умолчанию.
  • seen[]++ является общей идиомой Awk, которая либо создает запись для , входное слово, в ассоциативном массиве seen, если он еще не существует, или увеличивает ее количество вхождений.

  • !seen[]++ поэтому возвращает true только для первый происшествия данного слова (где seen[] неявно равно нулю / пустой строке;++ это в должности-increment, и поэтому не вступает в силу до после состояние оценивается)

  • {printf "%s%s",sep,; sep=" "} печатает слово под рукой , предшествует разделитель sep, который неявно пустой строка первый слово, но одно место для последующих слов, из-за настройки sep для " " сразу после.


вот более гибкий вариант, который ручки любых пробелов между словами; он работает с GNU Awk и Mawk[1]:

awk -v RS='[[:space:]]+' '{if (!seen[]++){printf "%s%s",sep,; sep=" "}}' <<<"$variable"
  • -v RS='[[:space:]]s+' говорит Awk разделить входные данные на записи любым сочетанием пробелов, вкладок и новых строк.

[1] к сожалению, BSD / OSX Awk (в строгом соответствии с POSIX spec), не поддерживает использование регулярные выражения или даже многосимвольные литералы как RS входной разделитель записей.


вы можете использовать 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

используя ассоциативные массивы в 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 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