Given the string foo-bar, what is a good way to convert it to Foo Bar?
Using Bash, OSX.
Start by changing dashes to spaces, like:
sed 's/-/ /g'
Uppercasing the first letter is already solved (go upvote them; or just mark this question a duplicate of that one).
Then combine them:
sed -e 's/-/ /g' -e 's/\b\(.\)/\u\1/g'
$ echo "foo-bar-baz-nonce" | sed -e 's/-/ /g' -e 's/\b\(.\)/\u\1/g'
Foo Bar Baz Nonce
$
function format { sed -e 's/-/ /g' -e 's/\b\(.\)/\u\1/g' }
sed will have difficulties w/ \b - I could be wrong about that though.
foo uar uaz nonce.
gsed and using that instead. Works perfectly.
$ cat file
aaa-zzz-eee-rrr
foo-bar
code
$ perl -ne 'print join " ", map { ucfirst } split /-/' file
Aaa Zzz Eee Rrr
Foo Bar
while IFS='-' read -r -a words; do
printf '%s\n' "${words[@]^}" | paste -sd ' '
done < file
Output:
Aaa Zzz Eee Rrr
Foo Bar
printf '%s\n' "${words[@]^}" | paste -sd ' ' is easier done with printf '%s\n' "${words[*]^}"
For the splitting in a POSIX shell you could do:
set -f; IFS=-; set -- $1; IFS=' '
There - now all of your dashes are spaces and you can have the entire string in "$*" or else each space - previously dash - separated string in $1 $2 $3 ... (the total count of which is available to you in "$#") or you can get them as an expandable argument list in "$@".
Now, for the case conversion you can do:
IFS='
'; for c in $(printf %.1s "$@" | dd cbs=1 conv=unblock,ucase)
do set -- "$@" "$c${1#[!"$IFS"]}"; shift; done
Here's the whole thing by example:
set -f -- aaa-zzz-eee-rrr-foo-bar
IFS=-; set -- $1; IFS='
'; for c in $(printf %.1s "$@" | dd cbs=1 conv=unblock,ucase)
do set -- "$@" "$c${1#?}"
shift; done; printf %s\\n "$@"
which prints:
Aaa
Zzz
Eee
Rrr
Foo
Bar
...which isn't space-separated I know - I used "$@". It is space-separated in "$*" because I set $IFS's first byte to space - the delimiter is definable in that way. For example:
IFS=k; printf %s\\n "$*"; IFS=' '; printf %s\\n "$*"
...which prints...
AaakZzzkEeekRrrkFookBar
Aaa Zzz Eee Rrr Foo Bar
You can save it any time of course:
IFS=-; dashes="$*"; IFS=' '; spaces="$*"; IFS=; empty="$*"
printf %s\\n "$dashes" "$spaces" "$empty"
...which prints:
Aaa-Zzz-Eee-Rrr-Foo-Bar
Aaa Zzz Eee Rrr Foo Bar
AaaZzzEeeRrrFooBar
This is a more complex subject but it can be used to great effect in these areas. It is important to protect data integrity though - use set -f when splitting in the shell to avoid filename generation on [?* and quote expansions you don't intend to split.
The dd command above - as it should be noted - is likely only to be very effective in an ASCII locale. In others you should probably look to recode or iconv or similar.
If that string is part of a file or output:
sed 's/foo-bar/Foo Bar/g' file
Assuming there are no "strange" characters in the string (/, \, &):
string="foo-bar"
set -- $(echo "${string//-/ }")
replstr="$(for word in $*; do echo -n "${word^} "; done)"
replstr="${replstr% }"
sed "s/${string}/${replstr}/g' file
Step 1: Replace the - with a space
For this you can use tr command as follows:
tr "-" " "
Step 2: Make first letter of every word capital
For this, you can find the word boundary using \b and make immediate letter capital. The . represents immediate letter after word boundary \b, \U makes it capital and & will keep all other letters in the letters as they were.
sed -e 's/\b./\U&/g'
Check:
echo "foo-bar-foo-bar" | tr "-" " " | sed -e 's/\b./\U&/g'
Foo Bar Foo Bar
foo Ubar foo Ubar.
echo "foo-bar-foo-bar" | tr "-" " " | sed -e 's/\b./\U&/g'
in bash
v=foo-bar && v=$(IFS=- read -ra x <<<"$v"; printf '%s' "${x[*]^}") && printf '%s\n' "$v"
Foo Bar
In python
v=foo-bar python -c 'import os, string
print string.capwords(" ".join(os.environ["v"].split("-")))
'
Foo Bar
newstring=$(
IFS=-; set -f
array=($string)
IFS=' '
printf '%s' "${array[*]^}"
)
This first splits the string in $string on dashes, creating an array. To do this, we use $string unquoted with $IFS set to a dash character. We also disable filename globbing with set -f to avoid accidentally picking up any filenames if the string is split into something resembling a filename globbing pattern.
We then concatenate the elements of the array with a space character as the delimiter and, at the same time, upper-case the first character of each element. The upper-casing of each array element is done using {$array[*]^}. This creates a single string from the elements of the array, delimited by the space in $IFS.
Modifying the special IFS variable and setting set -f to avoid globbing are local operations in the subshell that constitute the command substitution.
For bash you can use sed and tr:
$ echo "foo-bar" | sed -E "s/[[:alnum:]]+/\u&/g" | tr '-' ' '
Foo Bar
sed on macOS (which is what the user in the question is using) does not support \u to upper-case the replacement string in the s command. The command that you show generates the string ufoo ubar.