Bash Script While Loop Menu: 5 Easy Fixes, Avoid Bugs

A bash script while loop with a case statement is the classic way to build a text menu: show the options, read a choice, act, repeat. The old version of this post gave a full menu script with colours and a log through tee. It works when you type correctly at a terminal. This rewrite drives the same script with piped input in Bash 5.2.37 on Debian 13, and finds four bugs: an endless loop when the input ends, a log full of colour codes, a failure under sh, and an unquoted echo. A fixed version follows.

How the bash script while loop menu works

The script runs while true, prints the menu with echo -e and colour variables, reads the choice into a variable named case, and uses case "$case" in 1) ... 5) break;; esac. Options 1 to 4 ask for confirmation with read -p and a regular expression test with [[ ... =~ ... ]]. The whole loop ends with done | tee -a /tmp/log_$DATE, so everything the loop prints goes to the screen and to a log file.

With the input 1, y, 2, y, Huu, 5 the menu worked: it printed the site name after the first confirmation, asked for a name after the second, and ended with "Exit". Note one effect of the pipe to tee: the prompt text of read -p ("Are you sure?") did not appear in the captured output, because bash prints that prompt only when the input is a terminal, as the Bash manual says for read -p. The line ended as >> www.huuphan.com! ^_^.

Bug 1 in the bash script while loop: end of input

If the input ends, read fails and returns an empty variable, and the loop starts again with no way out. With printf "1\n" | timeout 2 ./menu_list the menu was printed 120692 times in two seconds, and the log file /tmp/log_20261101 grew to 19312035 bytes, about 19 MB. This happens when the script runs from cron, from a closed terminal or after Ctrl+D, and it can fill a disk. The fix is to leave the loop when read fails: read -r choice || break. The same kind of endless loop is described in the Bash yes/no prompt post, and loops that never stop are covered in the infinite loops post.

Bash script while loop menu: the loop repeats 120692 times when input ends, fails under sh, expands a wildcard and saves colour codes in the log
Four bugs of the old bash script while loop menu.

Bug 2 and 3 of the bash script while loop: colour codes and the shell

The colours are written with echo -e into the same stream that goes to tee. After two test runs the log had 52 lines, 36 of them with the colour sequence [0;34m. A log with escape characters is hard to read with grep or less. Define the colours only when the output is a terminal (if [ -t 1 ]), as the fixed script does. Also, the script needs Bash: under sh it stopped with ./menu_list: 21: Syntax error: "(" unexpected (expecting "then"), which comes from the [[ =~ ]] test. Keep #!/bin/bash or use a plain case for the answer, as shown below.

Bug 4 of the bash script while loop: unquoted variables and the case variable

Options 2 to 4 print the answer with echo $NAME. With a value of " two spaces *", the unquoted echo printed two spaces menu_list menu_new: the spaces were collapsed and the star was replaced by the file names of the directory. The quoted echo "$NAME" printed the text unchanged. The choice variable named case works, because keywords are not reserved as variable names, but a name like choice is easier to read. Finally, an unknown choice such as 9 is silently ignored: the menu is shown again without any message.

A fixed bash script while loop menu

confirm() {
  local answer
  read -r -p "Are you sure? [y/N] " answer || return 1
  case ${answer,,} in y|yes) return 0 ;; *) return 1 ;; esac
}

while true; do
  printf '%s\n' "" "Menu Lists" " 1) My blog" " 2) Your name" " 5) Quit"
  read -r -p ">> " choice || { echo "end of input, leaving"; break; }
  case $choice in
    1) if confirm; then echo "www.huuphan.com"; else echo "Nothing!"; fi ;;
    2) if confirm; then read -r -p "Your name: " name; printf 'Hello, %s\n' "$name"; else echo "Nothing!"; fi ;;
    5|q|Q) break ;;
    "") ;;
    *) echo "Unknown choice: $choice" ;;
  esac
done | tee -a "$LOG"

In the tests, the input 9, 1, Y, 2, yes, Huu Phan, q printed Unknown choice: 9, the site name, Hello, Huu Phan and ended. The log of that run had 23 lines and no colour codes. With the input 1 only, the script printed end of input, leaving and stopped, with exit code 0. The input 1, n, 5 printed Nothing!. The confirmation uses a plain case with ${answer,,} (lower case, a Bash 4 feature) instead of a regular expression.

Common mistakes in a bash script while loop menu

  • Not handling end of input, so that the loop spins forever.
  • Logging coloured text without checking for a terminal.
  • Unquoted variables in echo.
  • No default branch in case, so typos are ignored.
  • Using Bash-only tests in a script that may run with sh.

How to test a bash script while loop before you ship it

Use the same three tests every time. First, feed the normal path through a pipe and compare the output with what you expect. Second, feed an unknown choice and an empty line, and check that the script says something. Third, feed nothing at all (printf '' | timeout 2 ./menu) and check that it stops by itself. The old script passed only the first test. The third test is the one that catches the 19 MB log, and it takes two seconds to run.

FAQ

Can I use select instead of a bash script while loop? Yes, select builds a numbered menu by itself; it was not tested in this lab.

How do I test a menu script without typing? Pipe the answers in with printf, as the lab did, and add timeout so that a loop bug cannot run forever.

Comments

Post a Comment

Popular posts from this blog

How to Play Minecraft Bedrock Edition on Linux: A Comprehensive Guide for Tech Professionals

Linux desktop frozen? My 5 go-to tricks to try - before forcing a hard reboot

The Ultimate Guide: How to Set Up DXVK in Wine on Linux for Enhanced Gaming Performance