Skip to main content

2. Branching with case

The command case is a multiple-choice command.

  1. Let's see an example that implements a menu program with case:

    case-menu.sh
    #!/bin/bash

    # case-menu: a menu driven system information program

    clear
    echo "
    Please Select:

    1. Display System Information
    2. Display Disk Space
    3. Display Home Space Utilization
    0. Quit
    "
    read -p "Enter selection [0-3] > "

    case "$REPLY" in
    0) echo "Program terminated."
    exit
    ;;
    1) echo "Hostname: $HOSTNAME"
    uptime
    ;;
    2) df -h .
    ;;
    3) if [[ "$(id -u)" -eq 0 ]]; then
    echo "Home Space Utilization (All Users)"
    du -sh /home/*
    else
    echo "Home Space Utilization ($USER)"
    du -sh "$HOME"
    fi
    ;;
    *) echo "Invalid entry" >&2
    exit 1
    ;;
    esac
    vim case-menu.sh

    We have seen this example before, implemented with if and it is clear that with case it is much simpler.

    ./case-menu.sh

    case attempts a match against the specified patterns. When a match is found, the commands associated with the specified pattern are executed. After a match is found, no further matches are attempted.

  2. The patterns used by case are the same as those used by pathname expansion. For example:

    • a) -- matches the character "a"
    • [[:alpha:]]) -- matches any alphabetic character
    • ???) -- matches 3 characters
    • *.txt) -- matches anything that ends in .txt
    • *) -- matches anything

    It is good practice to include *) as the last pattern in a case command, to catch any values that did not match a previous pattern.

    Let's see an example script with patterns:

    case-patterns.sh
    #!/bin/bash

    read -p "enter word > "

    case "$REPLY" in
    [[:alpha:]]) echo "it is a single alphabetic character" ;;
    [ABC][0-9]) echo "it is A, B, or C followed by a digit" ;;
    ???) echo "it is three characters long" ;;
    *.txt) echo "it is a word ending in '.txt'" ;;
    *) echo "it is something else" ;;
    esac
    vim case-patterns.sh
    ./case-patterns.sh    # enter: x
    ./case-patterns.sh    # enter: B2
    ./case-patterns.sh    # enter: foo.txt
    ./case-patterns.sh    # enter: xyz
    ./case-patterns.sh    # enter: ab
  3. It is also possible to combine multiple patterns using the vertical bar character as a separator. Let's see a modified menu program that uses letters instead of digits for menu selection:

    case-menu-l.sh
    #!/bin/bash

    # case-menu: a menu driven system information program

    clear
    echo "
    Please Select:

    A. Display System Information
    B. Display Disk Space
    C. Display Home Space Utilization
    Q. Quit
    "
    read -p "Enter selection [A, B, C or Q] > "

    case "$REPLY" in
    q|Q) echo "Program terminated."
    exit
    ;;
    a|A) echo "Hostname: $HOSTNAME"
    uptime
    ;;
    b|B) df -h .
    ;;
    c|C) if [[ "$(id -u)" -eq 0 ]]; then
    echo "Home Space Utilization (All Users)"
    du -sh /home/*
    else
    echo "Home Space Utilization ($USER)"
    du -sh "$HOME"
    fi
    ;;
    *) echo "Invalid entry" >&2
    exit 1
    ;;
    esac
    vim case-menu-l.sh

    Notice how the new patterns allow for entry of both uppercase and lowercase letters.

    ./case-menu-l.sh
  4. When a pattern is matched, the corresponding actions are executed, and ;; makes sure that processing is stopped (without trying to match the following patterns). If we want instead to try matching them as well, we can use ;;& instead, as in this example:

    case4.sh
    #!/bin/bash
    # case4: test a character

    # -n 1: read only one char and don't wait for enter to be pressed
    read -n 1 -p "Type a character > "
    echo
    case "$REPLY" in
    [[:upper:]]) echo "'$REPLY' is upper case." ;;&
    [[:lower:]]) echo "'$REPLY' is lower case." ;;&
    [[:alpha:]]) echo "'$REPLY' is alphabetic." ;;&
    [[:digit:]]) echo "'$REPLY' is a digit." ;;&
    [[:graph:]]) echo "'$REPLY' is a visible character." ;;&
    [[:punct:]]) echo "'$REPLY' is a punctuation symbol." ;;&
    [[:space:]]) echo "'$REPLY' is a whitespace character." ;;&
    [[:xdigit:]]) echo "'$REPLY' is a hexadecimal digit." ;;&
    esac
    vim case4.sh
    ./case4.sh    # enter: a
    ./case4.sh    # enter: X
    ./case4.sh    # enter: +
Loading asciinema cast...