Upper- or lower-casing strings in bash and zsh

String comparison in most programming languages is case-sensitive. That means that the string 'A' and 'a' are considered different. Humans usually don’t think that way, so there is bound to be trouble and confusion.

If you are looking at single letters, the bracket expansion can be quite useful:

case $input in
        # handle 'a'
        # handle 'b'
        # handle 'q': quit
        exit 0
        echo "Option is not available. Please try again"

However, for longer strings the bracket expansion gets cumbersome. To cover all case combinations of the word cat you need [cC][aA][tT]. For longer, or unknown strings, it is easier to convert a string to uppercase (all capitals) or lower case before comparing.

sh and bash3

Bash3 and sh have no built-in means to convert case of a string, but you can use the tr tool:

name="John Doe"
# sh
echo $(echo "$name" |  tr '[:upper:]' '[:lower:]' )
john doe

# bash3
echo $(tr '[:upper:]' '[:lower:]' <<< "$name")
john doe

Switch the [:upper:] and [:lower:] arguments to convert to upper case.

There are many other tools available that can provide this functionality, such as awk or sed.

Bash 5

Bash 5 has a special parameter expansion for upper- and lowercasing strings:

name="John Doe"
echo ${name,,}
john doe
echo ${name^^}


In zsh you can use expansion modifiers:

% name="John Doe"
% echo ${name:l}
john doe
% echo ${name:u}

You can also use expansion flags:

% name="John Doe"    
% echo ${(L)name}     
john doe
% echo ${(U)name}

In zsh you can even declare a variable as inherently lower case or upper case. This will not affect the contents of the variable, but it will automatically be lower- or uppercased on expansion:

% typeset -l name      
% name="John Doe"
% echo $name        
john doe
% typeset -u name      
% echo $name        

Published by


Mac Admin, Consultant, and Author

7 thoughts on “Upper- or lower-casing strings in bash and zsh”

  1. I like your Bash5 suggestion, but it does not work for me:
    $ name=”John Doe”
    $ echo ${name,,}
    -bash: ${name,,}: bad substitution
    $ bash –version
    GNU bash, version 5.0.16(1)-release (x86_64-apple-darwin18.7.0)

      1. I copy-pasted your example. But just to be safe, I just re-typed it and encounter the same problem. (Indeed, I found your page because I’ve had this problem and have been searching for answers.) Even this simple example always fails. As noted in my original comment, I am using Bash5 on MacOS Mojave,

        [MacOS:~]$ name=”John Doe”
        [MacOS:~]$ echo ${name,,}
        -bash: ${name,,}: bad substitution
        [MacOS:~]$ echo ${name^^}
        -bash: ${name^^}: bad substitution

      2. Interesting. Different than what I get when I run bash –version

        [MacOS:~]$ echo $BASH_VERSION
        [MacOS:~]$ bash –version
        GNU bash, version 5.0.16(1)-release (x86_64-apple-darwin18.7.0)
        Copyright (C) 2019 Free Software Foundation, Inc.
        License GPLv3+: GNU GPL version 3 or later

        This is free software; you are free to change and redistribute it.
        There is NO WARRANTY, to the extent permitted by law.

      3. Yes, so you installed bash 5. The bash v5 binary is (probably) installed at /usr/local/bin/bash. Since /usr/local/bin is in the default PATH before /bin your shell picks up the new bash before the original /bin/bash. That is why bash —version returns 5

        However, your default shell, the one that starts when you open a Terminal window is still the built-in /bin/bash (v3.2)

        I explain this here: https://scriptingosx.com/2019/02/install-bash-5-on-macos/

      4. Ahah. I see. Frustrating.

        I care more about scripting than my Terminal shell… so I will need to think about whether to use the ‘env’ method or hard-coding the path to bash5 in my shebang.

        Thank you.

Comments are closed.