Read

Module 9 · Shell scripting basics

Reading input and a menu script

Ask the user questions with printf and read, branch on the answer with case, and assemble the module's pieces into a small interactive menu.

What you will learn

  • Prompt and read a line portably with `printf` + `read -r`.
  • Use `case … esac` with patterns and `|` alternatives.
  • Build a looped menu that runs commands and quits cleanly.

Scripts do not have to be silent batch jobs. read pauses until the user types a line and Enter, then stores the line in a variable. Show a question first with printf, not echo, because printf lets you leave the cursor on the same line. In bash you may have seen read -p "Name: " name; BusyBox read does not have -p, so the two-step form is the portable one and works everywhere.

~% cat lab/l90/ask.sh
#!/bin/sh
printf 'Your name: '
read -r name
[ -z "$name" ] && name="stranger"
echo "Hello, $name!"
~% lab/l90/ask.sh
Your name: Ada
Hello, Ada!
~% echo Linus | lab/l90/ask.sh
Your name: Hello, Linus!

The last line shows something important: read takes its input from standard input, so a script that asks questions can also be fed from a pipe or a file. That is how the checker for this lesson tests your scripts, and it is why interactive scripts remain automatable. The prompt and the answer land on the same line when the input is not a keyboard, which is harmless; if it bothers you, print prompts to stderr with printf '…' >&2.

case: branching on a value

An if/elif ladder comparing one variable against many strings is clumsy. case does it directly: it takes a value and a list of patterns, runs the block after the first pattern that matches, and stops. Patterns use the same wildcards as file names, *, ?, [a-z], and | separates alternatives. Each block ends with ;;, and *) at the end is the catch-all. The whole thing closes with esac.

case "$answer" in
    y|Y|yes)   echo "continuing" ;;
    n|N|no)    echo "stopped"; exit 0 ;;
    *.txt)     echo "that looks like a file name" ;;
    "")        echo "empty answer" ;;
    *)         echo "unknown: $answer" ;;
esac

Putting it together: a menu

~% cat lab/l90/menu.sh
#!/bin/sh
# A tiny system menu
show_menu() {
    echo "1) List files here"
    echo "2) Disk usage"
    echo "3) Who am I"
    echo "q) Quit"
    printf 'Choice: '
}

while true; do
    show_menu
    read -r choice
    case "$choice" in
        1) ls -l ;;
        2) df -h / ;;
        3) id ;;
        q|Q) echo "Bye"; break ;;
        *) echo "Unknown option: $choice" >&2 ;;
    esac
    echo
done
~% lab/l90/menu.sh
1) List files here
2) Disk usage
3) Who am I
q) Quit
Choice: 3
uid=0(root) gid=0(root)

1) List files here
...
Choice: q
Bye

Every tool from this module is in those twenty lines: a function (show_menu), an infinite while with break, read, case, an error on stderr and an exit status. When a menu option needs confirmation, nest another printf/read pair and a case on y|Y. When read hits end of input, in a pipe for instance, it fails; in a while true loop you should also break on that, with read -r choice || break, or the loop spins forever printing the menu.

Commands in this lesson

CommandWhat it does
printf 'Question: 'Prompt without a newline.
read -r varRead one line into var.
read -r a bFirst word into a, the rest into b.
IFS=: read -r a b c < fileSplit a colon-separated line.
case "$v" in pat) …;; *) …;; esacPattern-based branching.
read -r x || breakLeave the loop at end of input.

Quiz

  1. Why `printf 'Name: '` instead of `echo "Name: "` before `read`?

    • `echo` adds a newline, so the answer would be typed on the next line
    • `echo` cannot print colons
    • `read` only works after `printf`
  2. In a `case`, what does `y|Y|yes)` mean?

    • Run this branch for any of those three values
    • Pipe the value through `Y` and `yes`
    • A syntax error
  3. What closes a `case` block?

    • `done`
    • `esac`
    • `fi`
  4. `echo Ada | ./ask.sh` works even though the script uses `read`. Why?

    • `read` reads from standard input, which the pipe provides
    • `echo` simulates key presses
    • It does not work; `read` needs a keyboard

Practice

  1. Write an executable script `/root/lab/l90/ask.sh` that reads one line (a name) from standard input and prints `Hello, NAME!` — so `echo Ada | ./ask.sh` prints `Hello, Ada!`. A prompt is optional.

  2. Write an executable script `/root/lab/l90/menu.sh` that reads one choice with `read` and uses `case`: `1` runs `ls /root/lab/l90`, `2` runs `df -h`, anything else prints `Unknown option`.

  3. `/root/lab/l90/record.txt` holds one line like `ada:42:Lovelace`. Write an executable script `/root/lab/l90/split.sh` that reads it with `IFS=: read` into three variables and prints `ada is 42`.

Open this lesson in the app to do the tasks in a real Linux machine and have them checked.