строчные + заглавные + объединить слова строки в оболочке (например, bash)

Как использовать заглавные буквы + объединять слова в строке?
(первая буква в верхнем регистре, а все остальные буквы в нижнем регистре)

пример:
input = "jAMeS bOnD"
output = "JamesBond"


person oHo    schedule 09.06.2014    source источник


Ответы (5)


Манипуляции со строками доступны в bash версии 4:

  • ${variable,,}, чтобы строчные все буквы
  • ${variable^} до заглавной первой буквы каждого слова
  • используйте ${words[*]^} вместо ${words[@]^} для сохранения некоторых строк скрипта

И другие улучшения от mklement0 (см. Его комментарии):

  • Имена переменных в нижнем регистре, потому что имена в верхнем регистре могут конфликтовать с переменными среды.
  • Дайте значимые имена переменным (например, ARRAY -> words)
  • Используйте local, чтобы не повлиять на IFS вне функции (одного раза достаточно)
  • Используйте local для всех остальных локальных переменных (переменную можно сначала объявить, а затем присвоить)
  • ARRAY=( $LOWERCASE ) may expands globs (filename wildcards)
    • temporarily disable Pathname Expansion using set -f or shopt -so noglob
    • или используйте read -ra words <<< "$input" вместо words=( $input )

Конечная функция:

capitalize_remove_spaces()
{
  local words IFS
  read -ra words <<< "${@,,}"
  IFS=''
  echo "${words[*]^}"
}

Если вы хотите сохранить только буквенно-цифровые символы, расширяет встроенную переменную IFS непосредственно перед read -ra words операция:

capitalize_remove_punctuation()
{
  local words IFS=$' \t\n-\'.,;!:*?' #Handle hyphenated names and punctuation
  read -ra words <<< "${@,,}"
  IFS=''
  echo "${words[*]^}"
}

Примеры:

> capitalize_remove_spaces 'jAMeS bOnD'
JamesBond

> capitalize_remove_spaces 'jAMeS bOnD *'
JamesBond*

> capitalize_remove_spaces 'Jean-luc GRAND-PIERRE'
Jean-lucGrand-pierre

> capitalize_remove_punctuation 'Jean-luc GRAND-PIERRE'
JeanLucGrandPierre

> capitalize_remove_punctuation 'Jean-luc GRAND-PIERRE *'
JeanLucGrandPierre
person oHo    schedule 09.06.2014
comment
+1; удобные вещи; несколько советов: рекомендуется не использовать имена переменных в верхнем регистре при программировании оболочки, чтобы избежать конфликтов с переменными среды (хотя мне бы очень не хотелось видеть удивительно сюрреалистический $LOWERCASE go :). Создание массивов слов с ARRAY=( $LOWERCASE ) имеет ловушку: строка подлежит расширению имени пути, поэтому результаты будут неожиданными, если строка содержит глобус, такой как *. Включение set -f (shopt -so noglob) (временно) может исправить это. Другой вариант - использовать read -a для создания массива (см. Мой ответ). - person mklement0; 11.06.2014
comment
Я предлагаю использовать локальные переменные в ваших функциях оболочки, например: local IFS='' - это дает дополнительное преимущество в виде отсутствия необходимости восстанавливать $IFS при выходе из функции. - person mklement0; 11.06.2014
comment
Я ценю ваше стремление улучшить свой ответ - эти функции элегантны. И последнее предложение: я предлагаю сделать все переменные в функциях local. Плюс к этому придирка: пока работает повторение local при назначении $IFS во второй раз, я бы объявил переменную только один раз, вверху. Например, во второй функции вы можете использовать local words IFS=$' \t\n-\'.,;!:*?' (или использовать local, чтобы просто объявить, затем назначить позже), а затем просто IFS='' позже. - person mklement0; 11.06.2014
comment
Спасибо @ mklement0 :-D Я только что применил ваш последний совет :-) И я также сократил ответ ;-) Ура - person oHo; 18.06.2014

Из других сообщений я придумал этот рабочий сценарий:

str="jAMeS bOnD"
res=""
split=`echo $str | sed -e 's/ /\n/g'` # Split with space as delimiter
for word in $split; do
    word=${word,,} # Lowercase
    word=${word^} # Uppercase first letter
    res=$res$word # Concatenate result
done

echo $res

Использованная литература:

person julienc    schedule 09.06.2014
comment
Нет необходимости в команде sed и промежуточной переменной $split: использование строки без кавычек с for выполняет разделение слов также с пробелами, а не только с новой строкой; таким образом, вы можете просто сказать: for word in $str; do. В любом случае есть ловушка: строки без кавычек, используемые с for, подлежат расширению имени пути, поэтому результаты будут неожиданными, если строка содержит глобус, например *. Включение set -f (shopt -so noglob) (временно) может исправить это. Обратите внимание, что для вашего решения требуется bash 4 или выше. - person mklement0; 11.06.2014

Использование awk немного многословно, но выполняет свою работу:

s="jAMeS bOnD"
awk '{for (i=1; i<=NF; i++)  
   printf toupper(substr($i, 1, 1)) tolower(substr($i,2)); print ""}' <<< "$s"
JamesBond
person anubhava    schedule 09.06.2014
comment
Хорошо, но GNU Awk 3.1.7 говорит awk: (FILENAME=- FNR=1) fatal: printf: no arguments. Мне пришлось переместиться вниз printf в той же строке, что и toupper.... Ваше здоровье :) - person oHo; 09.06.2014

Вот bash 3+ решение, которое использует tr для преобразования регистра (операторы преобразования регистра (,, ^, ...) были введены в bash 4):

input="jAMeS bOnD"

read -ra words <<<"$input" # split input into an array of words
output="" # initialize output variable
for word in "${words[@]}"; do # loop over all words
  # add capitalized 1st letter
  output+="$(tr '[:lower:]' '[:upper:]' <<<"${word:0:1}")"
  # add lowercase version of rest of word
  output+="$(tr '[:upper:]' '[:lower:]' <<<"${word:1}")"
done

Примечание:

  • Конкатенация (удаление пробелов между словами) происходит неявно, всегда напрямую добавляя к выходной переменной.
  • Заманчиво захотеть использовать words=( $input ) для разделения входной строки на массив слов, но есть одна проблема: строка подлежит расширению имени пути, поэтому, если слово оказывается действительным глобусом (например, , *), он будет расширен (заменен соответствующими именами файлов), что нежелательно; использование read -ra для создания массива позволяет избежать этой проблемы (-a считывает в массив, -r отключает интерпретацию \ символов во входных данных).
person mklement0    schedule 10.06.2014

person    schedule
comment
Обязательно укажите объяснение своего ответа, а не просто код. - person AMACB; 26.01.2016
comment
Спасибо за отзыв @AMACB Я добавил комментарий. Вышеупомянутое было просто примером, который можно использовать для форматирования строк. - person sweluhu; 03.11.2018