4

How to capitalize+concatenate words of a string?
(first letter uppercase and all other other letters lowercase)

example:
input = "jAMeS bOnD"
output = "JamesBond"

oHo
  • 51,447
  • 27
  • 165
  • 200

5 Answers5

7

String manipulation available in version 4:

  • ${variable,,} to lowercase all letters
  • ${variable^} to uppercase first letter of each word
  • use ${words[*]^} instead of ${words[@]^} to save some script lines

And other improvements from mklement0 (see his comments):

  • Variable names in lower-case because upper-case ones may conflict with environment variables
  • Give meaningful names to variables (e.g. ARRAY -> words)
  • Use local to avoid impacting IFS outside the function (once is enougth)
  • Use local for all other local variables ( variable can be first declared, and later assigned)
  • ARRAY=( $LOWERCASE ) may expands globs (filename wildcards)
    • temporarily disable Pathname Expansion using set -f or shopt -so noglob
    • or use read -ra words <<< "$input" instead of words=( $input )

Ultimate function:

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

If you want to keep alphanumeric characters only, extends the IFS built-in variable just before the read -ra words operation:

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

Examples:

> 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
Community
  • 1
  • 1
oHo
  • 51,447
  • 27
  • 165
  • 200
  • 1
    +1; handy stuff; a few suggestions: it's good practice not to use all-uppercase variable names in shell programming, so as to avoid conflicts with environment variables (though I'd hate to see the wonderfully surrealistic `$LOWERCASE` go :). Creating your word arrays with `ARRAY=( $LOWERCASE )` has a pitfall: the string is subject to _pathname expansion_ so the results will be unexpected if the string contains a glob such as `*`. Turning `set -f` (`shopt -so noglob`) on (temporarily) could fix this. Another option is to use `read -a` to create the array (see my answer). – mklement0 Jun 11 '14 at 06:36
  • 1
    I suggest using _local_ variables in your shell _functions_, e.g.: `local IFS=''` - this has the added benefit of not having to restore `$IFS` on leaving the function. – mklement0 Jun 11 '14 at 06:38
  • 1
    I appreciate your dedication to improving your answer - these functions are elegant. One final suggestion: I suggest making _all_ variables in the functions _local_. Plus a quibble: while it works to repeat `local` when assigning `$IFS` the 2nd time, I'd only declare the variable once, at the top. E.g., in the 2nd function you could use `local words IFS=$' \t\n-\'.,;!:*?'` (or use `local` to merely _declare_, then _assign_ later), and then simply `IFS=''` later. – mklement0 Jun 11 '14 at 15:26
  • 1
    Thank you @mklement0 :-D I have just applied your last advice :-) And I have also shorten the answer ;-) Cheers – oHo Jun 18 '14 at 13:48
2

Here's a bash 3+ solution that utilizes tr for case conversion (the case conversion operators (,, ^, ...) were introduced in 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

Note:

  • Concatenation (removal of whitespace between words) happens implicitly by always directly appending to the output variable.
  • It's tempting to want to use words=( $input ) to split the input string into an array of words, but there's a gotcha: the string is subject to pathname expansion, so if a word happens to be a valid glob (e.g., *), it will be expanded (replaced with matching filenames), which is undesired; using read -ra to create the array avoids this problem (-a reads into an array, -r turns off interpretation of \ chars. in the input).
mklement0
  • 382,024
  • 64
  • 607
  • 775
1

From other posts, I came up with this working script:

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

References:

Community
  • 1
  • 1
julienc
  • 19,087
  • 17
  • 82
  • 82
  • 1
    There is no need for the `sed` command and the intermediate `$split` variable: using an unquoted string with `for` performs word splitting also with spaces, not just newlines; thus, you could just say: `for word in $str; do`. Either way, there is a pitfall: unquoted strings used with `for` are subject to _pathname expansion_, so the results will be unexpected if the string contains a glob such as `*`. Turning `set -f` (`shopt -so noglob`) on (temporarily) could fix this. Note that your solution requires bash _4_ or higher. – mklement0 Jun 11 '14 at 06:27
1

Using awk it is little verbose but does the job::

s="jAMeS bOnD"
awk '{for (i=1; i<=NF; i++)  
   printf toupper(substr($i, 1, 1)) tolower(substr($i,2)); print ""}' <<< "$s"
JamesBond
oHo
  • 51,447
  • 27
  • 165
  • 200
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • Fine, but GNU Awk 3.1.7 says `awk: (FILENAME=- FNR=1) fatal: printf: no arguments`. I had to move down `printf` in the same line as `toupper...`. Cheers :) – oHo Jun 09 '14 at 08:00
0
echo -e '\n' "!!!!! PERMISSION to WRITE in  /var/log/ DENIED !!!!!"
echo -e '\n'
echo "Do you want to continue?"
echo -e '\n' "Yes or No"
read -p "Please Respond_: " Response #get input from keyboard "yes/no"
#Capitalizing 'yes/no' with # echo $Response | awk '{print toupper($0)}' or echo $Response | tr [a-z] [A-Z] 
answer=$(echo $Response | awk '{print toupper($0)}') 
case $answer in
    NO)
        echo -e '\n' "Quitting..."
        exit 1
        ;;
    YES)
        echo -e '\n' "Proceeding..."
        ;;
esac
sweluhu
  • 11
  • 1