7

According to https://caseconverter.com/

“Upper Case” WHICH CONVERTS ALL THE LETTER INTO CAPITALS LIKE THIS.

“Lower Case” which converts all the letters into small letters like this.

“Proper Case” Which Converts The Text So Every Word Has Its First Letter Capitalised Like This

“Sentence Case”. This capitalises the first letter of each sentence, and converts the rest of the text to lower case. So the first letter after every full stop is automatically converted into a capital letter.

The first two can be accomplish easily with tr command.

user@linux:~$ tr [:lower:] [:upper:] <<< eXaMPLe
EXAMPLE
user@linux:~$ 

user@linux:~$ tr [:upper:] [:lower:] <<< eXaMPLe
example
user@linux:~$ 

or

user@linux:~$ tr [a-z] [A-Z] <<< eXaMPLe
EXAMPLE
user@linux:~$ 

user@linux:~$ tr [A-Z] [a-z] <<< eXaMPLe
example
user@linux:~$ 

What about the last two which are "Proper Case" and "Sentence Case"?

Is it possible?

If yes, please let me know.

If not, what is the alternative?

6 Answers 6

6

Note that unless your shell is fish which doesn't support the [...] globbing operator, you should quote those [:lower:], [A-Z], otherwise they could be expanded by the shell to the list of matching files in the current directory (or report an error if there's no match):

tr '[:lower:]' '[:upper:]'

Other notes:

  • the GNU implementation of tr only supports single byte characters, so in a UTF-8 locale, it will only capitalise English letters without diacritics.
  • tr '[A-Z]' '[a-z]' is fine, but you can also simply do tr A-Z a-z (in POSIX compliant implementations). However, it's only guaranteed to match only on ABCDEFGHIJKLMNOPQRSTUVWXYZ in the C/POSIX locale.

To Capitalise the first letter of every word, with the GNU implementation of sed, you can do:

sed -E "s/[[:alnum:]_'-]+/\u&/g"

We're including ', - and _ but no other punctuation characters so as to turn foo-bar2baz,foo into Foo-bar2baz,Foo.

That works with multi-byte characters, but generally not with combining characters as most locales won't consider them as alnum:

$ echo $'ste\u0301phane' | sed -E "s/[[:alnum:]_']+/\u&/g"
StéPhane

To consider those, you could switch to perl, where those can be matched with \pM:

$ echo $'ste\u0301phane chazelas' | perl -Mopen=locale -pe 's/[\w\pM'\''-]+/\u$&/g'
Stéphane Chazelas

Also note that that would turn first (whose first character is the ligature character) to FIrst. Using ucfirst() instead of uc() / \u would avoid that:

$ echo 'first second' | perl -Mopen=locale -pe 's/[\w\pM'\''-]+/ucfirst$&/ge'
First Second

For the sentence capitalisation, it's quite tricky, you have to capitalise the first letter found at the start of the text or after a sentence delimiter (like ., ?, …) or sentence introducer (¿, ¡), allowing any number of whitespace in between, but also things like (, [, ", , , «…). Depending on which language(s) you want to support, you may want to consider more.

You could do it with perl with something like:

perl -0777 -C -pe 's/(^|[.!?…⁇⁈⁉¿¡])[\s([{"`‶‷«]*\K\p{Ll}/ucfirst$&/ge'

Here assuming a UTF-8 locale and input and only covering a few of those cases.

In any case, that's not something that can be done with tr alone as tr transliterates every character, it can't be told to transliterate only some.

2

No, it's not possible to do with tr alone. tr has no understanding of words or sentences required to make this work.

As an alternative, you can try Perl with its powerful pattern engine:

$ perl -pe 's/\b[\p{L}\p{Pd}]+/ucfirst lc $&/ge' <<< 'A HEART-SHAPED BOX'
A Heart-shaped Box
$ perl -pe 's/\p{L}.*?[.?!]/ucfirst lc $&/ge' <<< 'for sale. baby shoes. never worn.'
For sale. Baby shoes. Never worn.

The above, however, is a quick-and-dirty solution and doesn't cover every possible case and tweak one might want to apply here (non-Latin alphabets and non-ASCII Latin letters, locale-dependent case, articles not being capitalised, proper nouns in sentences being capitalised, etc.). Then again, neither can tr do it.

1

tr only changes single characters to other single characters (or deletes them), and it has no sense of the context of any character. It can't therefore distinguish between a character at the beginning, middle, or end of a word. It does not even know what a "word" is.

Changing a text to title case (what you call "proper case") or to sentence case is impossible using tr.

You need a tool that you can use to give some context to the individual character with.

This is a naive GNU sed program that converts a text to title case by matching separate words and changing their first character:

$ sed 's/\<\([[:lower:]]\)\([[:alnum:]]*\)/\u\1\2/g' file
There Is No Danger On The Roof. There Is No Cow On The Ice.

The \< matches at transition point between a non-word character and a word character (i.e., at the start of a word). The rest of the regular expression matches a lower case letter followed by any number of alphanumeric characters. If it matches, it changes the lower case letter to an upper case letter and appends the rest of the word. The upper-casing of the first letter is using a GNU sed extension (this would not work most other sed implementations).

For sentence casing a text, another naive GNU sed variation:

$ sed 's/\<\([[:lower:]]\)\([^[:punct:]]*\)/\u\1\2/g' file
There is no danger on the roof. There is no cow on the ice.

This is more or less the same thing again, but instead of matching across a word of alphanumerical characters, we match across a string of characters that are not punctuation characters.

Note that this only works on really simple texts of the type that you show in the question. The second sed, would, for example, not properly cope with the question what's that?, due to ' being a punctuation character matched by [[:punct:]].

1

Using Raku (formerly known as Perl_6)

Sample Input:

~$ cat  file
The RIVER dee in Aberdeen can be cold,
so i shiver when i swim. Scotland is STILL beaUtIful though.
eveN when it RAINS? "YES! of course", is the obvious answer.

Upper Case:

~$ raku -ne '.uc.put;'  file
THE RIVER DEE IN ABERDEEN CAN BE COLD,
SO I SHIVER WHEN I SWIM. SCOTLAND IS STILL BEAUTIFUL THOUGH.
EVEN WHEN IT RAINS? "YES! OF COURSE", IS THE OBVIOUS ANSWER.

Lower Case:

~$ raku -ne '.lc.put;'  file
the river dee in aberdeen can be cold,
so i shiver when i swim. scotland is still beautiful though.
even when it rains? "yes! of course", is the obvious answer.

Proper Case, a.k.a. "Title Case Lower Case" (per string: uppercase the first letter and lowercase all others), or "Wordcase" (same but works on words and so does the heavy-lifting for you):

#NOTE: `tclc` code below does not capitalize words that starts with punctuation, 
#       [useful for "free-standing" bracketed markdown text]

~$ raku -ne 'put .words.map: *.tclc.join(" ");'  file
The River Dee In Aberdeen Can Be Cold,
So I Shiver When I Swim. Scotland Is Still Beautiful Though.
Even When It Rains? "yes! Of Course", Is The Obvious Answer.

#NOTE: `wordcase` function can take a list of proper_names (see final example).

~$ raku -ne '.wordcase.put;'  file
The River Dee In Aberdeen Can Be Cold,
So I Shiver When I Swim. Scotland Is Still Beautiful Though.
Even When It Rains? "Yes! Of Course", Is The Obvious Answer.

Sentence Case (accomplished with split or subst):

~$ raku -e 'my @a = [Z~] <. ! ? . ! ?>, "   \n\n\n".comb; my @b = slurp.split(@a, :p);  \
            put join "", [Z~] @b[0,2,4,6 ... Inf]>>.tclc, @b[1,3,5,7 ... Inf].map: *.value;'  file
The river dee in aberdeen can be cold,
so i shiver when i swim. Scotland is still beautiful though.
Even when it rains? "yes! Of course", is the obvious answer.

#OR (more simply, also capitalizes ` I `)

~$ raku -e 'slurp.tclc.subst(:global, / <[.!?]> \s <( <:P>? <:L> | \si\s )> /, {$/.uc // ""} ).put;' dee.txt
The river dee in aberdeen can be cold,
so I shiver when I swim. Scotland is still beautiful though.
Even when it rains? "Yes! Of course", is the obvious answer.

Sentence Case with Proper Names Capitalized (accomplished with wordcase and subst):

~$ raku -e 'my @txt = slurp.tclc.wordcase(:filter(&tc), :where({ .match(:ignorecase, :global, /  river | dee | aberdeen | scotland /) }));  \
            my @sep = [Z~] <. ! ? . ! ?>, "   \n\n\n".comb;  \
            for @sep -> $sep { @txt.=subst(:global, / $sep <( <:P>? <:L> | [\s i \s] )> /, {$/.uc // ""} ); };  \
            put @txt;'  file
The River Dee in Aberdeen can be cold,
so I shiver when I swim. Scotland is still beautiful though.
Even when it rains? "Yes! Of course", is the obvious answer.

#OR (more simply)

~$ raku -e 'my @proper = <river dee aberdeen scotland>;  \
            my @txt = slurp.tclc.wordcase(:filter(&tc), :where({ .match(:ignorecase, :global, / @proper /) }));  \
            @txt.=subst(:global, / <[.!?]> \s <( <:P>? <:L> | \si\s )> /, {$/.uc // ""} );  \
            put @txt;' dee.txt
The River Dee in Aberdeen can be cold,
so I shiver when I swim. Scotland is still beautiful though.
Even when it rains? "Yes! Of course", is the obvious answer.

Raku is a programming language in the Perl family. "Casing"-methods include lc, uc, tc, tclc, and wordcase. Regex matching/substitution parameters (a.k.a. adverbs) include :ignorecase and :samecase. Various combinations of these methods/adverbs can give you a nice "Sentence Case" return at the bottom.

Explaining just the final example, the file is slurp read-in-all-at-once, converted to tclc and then wordcase takes a @proper array of matches that get capitalized. Then a substution is performed where <[.!?]> \s sentence terminating characters followed by whitespace are located, and the first <:L> (Unicode letter) character following gets converted to uc uppercase (note the <:P>? <:L> allowing for initial punctuation). Lowercase "i" (a.k.a. regex [\s i \s]) is also converted to uppercase, for good measure.

[Of course, the final example above is just a start. Caveats: only three sentence terminators are handled, and "proper" names get individually capitalized, so that e.g. "River" is capitalized whether-or-not it is followed by "Dee". Final thanks to @kes for the text (via @Stéphane_Chazelas)].

http://raku.org

0

zsh has the L, U, C parameter expansion flags for the first three, csh-style :l / :u modifiers or ksh-style typeset -u/-l for the first too and could do the fourth with ksh-style ${param//pattern/replacement} using capture groups.

text='The RIVER dee in Aberdeen can be cold,
 so i shiver when i swim. Scotland is STILL beaUtIful though.
 eveN when it RAINS? "YES! of course", is the obvious answer.'
$ print -r -- ${(L)text}
the river dee in aberdeen can be cold,
 so i shiver when i swim. scotland is still beautiful though.
 even when it rains? "yes! of course", is the obvious answer.
$ print -r -- ${(U)text}
THE RIVER DEE IN ABERDEEN CAN BE COLD,
 SO I SHIVER WHEN I SWIM. SCOTLAND IS STILL BEAUTIFUL THOUGH.
 EVEN WHEN IT RAINS? "YES! OF COURSE", IS THE OBVIOUS ANSWER.
$ print -r -- ${(C)text}
The River Dee In Aberdeen Can Be Cold,
 So I Shiver When I Swim. Scotland Is Still Beautiful Though.
 Even When It Rains? "Yes! Of Course", Is The Obvious Answer.
$ print -r -- $text:l
the river dee in aberdeen can be cold,
 so i shiver when i swim. scotland is still beautiful though.
 even when it rains? "yes! of course", is the obvious answer.
$ print -r -- $text:u
THE RIVER DEE IN ABERDEEN CAN BE COLD,
 SO I SHIVER WHEN I SWIM. SCOTLAND IS STILL BEAUTIFUL THOUGH.
 EVEN WHEN IT RAINS? "YES! OF COURSE", IS THE OBVIOUS ANSWER.
$ typeset -l text
$ print -r -- $text
the river dee in aberdeen can be cold,
 so i shiver when i swim. scotland is still beautiful though.
 even when it rains? "yes! of course", is the obvious answer.
$ typeset -u text
$ print -r -- $text
THE RIVER DEE IN ABERDEEN CAN BE COLD,
 SO I SHIVER WHEN I SWIM. SCOTLAND IS STILL BEAUTIFUL THOUGH.
 EVEN WHEN IT RAINS? "YES! OF COURSE", IS THE OBVIOUS ANSWER.
$ set -o extendedglob
$ typeset +u text
$ print -r -- ${${text:l}//(#b)(((#s)|[.!?…⁇⁈⁉¿¡])[[:space:][\{\"\`‶‷«]#)([[:lower:]])/$match[1]${match[3]:u}}
The river dee in aberdeen can be cold,
 so i shiver when i swim. Scotland is still beautiful though.
 Even when it rains? "Yes! Of course", is the obvious answer.

With the same caveats as in my other answer.

0

I always thought it reasonable to assume that sentence case is a capital letter for the first letter of each sentence only. Not an unreasonabe assumption. A capital letter for each word in a sentence does not represent capitalisation as used in punctuation of a sentence.

Here is how to do this with sed in bash.
I'm using Arch linux kernel 6.2.13-arch1-1
and GNU Sed version 4.9.

text="The RIVER dee in Aberdeen can be cold, so i shiver when i swim. Scotland is STILL beaUtIful though. eveN when it RAINS? YES! of course."

text=$(echo "$text" | sed 's/.*/\L&/; s/\b[a-z]/\u&/')           # make whole string lowercase 
                                                                 # then make first letter of string upper case  
text=$(echo "$text" | sed 's/\([.!?] [a-z]\| i \)/\U\1/g')       # sentence case for all sentences punctuated by
                                                                 #   ". a", "? a", or "! a" where "a" can be any letter   
echo "$text"

         

So we start with
The RIVER dee in Aberdeen can be cold, so i shiver when i swim. Scotland is STILL beaUtIful though. eveN when it RAINS? YES! of course.

and we end with
The river dee in aberdeen can be cold, so I shiver when I swim. Scotland is still beautiful though. Even when it rains? Yes! Of course.

You can then go through and capatalise place names such as Aberdeen and Dee.

3
  • 1
    @stéphane-chazelas no, Kes the book is set in Nothern england. Kes here is a Scottish-Scando-Pictish name, derived from Kesog, one of St Columba's 12 disciples.
    – Kes
    May 5, 2023 at 3:17
  • My bad, sorry, I had misremembered it being set in Scotland. May 5, 2023 at 5:13
  • Not sure why my output text gets as its first letter is an introduced L? Also Aberdeen becomes Auberdeen. I'm on GNU bash, version 5.1.8(1)-release. May 8, 2023 at 22:10

You must log in to answer this question.