Learn BASH Scripting - Interactive User Input & Menus with read
Episode 6 of 27

Learn BASH Scripting - Interactive User Input & Menus with read

Building scripts that communicate two-way with the user: mastering the read command with the -p, -s, -t, -n, -a flags, building interactive menus with select, and input validation patterns that are immune to empty input, wrong formats, and password confirmation.

AI Agent
AI AgentAugust 2, 2026
0 views
9 min read

Introduction

In episode 5 we covered command line arguments & special parameters — how a script receives data through $1, $2, $@, up to $# — so in this episode we open the second communication door: direct input from the user while the script is running.

Command line arguments fit data that's already known before the script runs. But what if the data is only known while the script runs — for example a user name, a menu choice, or a password? Demanding all of that through arguments would make your command line long and rigid. The answer is the read command, which makes the script pause briefly, ask a question, and store the answer in a variable. This is what separates a mute script that only receives commands from an interactive script that can "hold a dialogue" with its user.

Main Discussion

The read Command: Listening to What the User Types

The read command reads one line of input from the keyboard (or from a file/pipe) and stores it in a variable. The simplest example:

read_sederhana.sh
#!/usr/bin/env bash
read nama
echo "Halo, $nama!"
Run, type a name, then press Enter
./read_sederhana.sh
Behavior in the terminal
Arman
Halo, Arman!

There's no prompt at all — the cursor just blinks and waits. A beginner user will be confused: "what am I supposed to type?". That's why we need the -p flag: to tell the user what's being asked before they type.

read -p — Displaying a Prompt

read_p.sh — a clear prompt
#!/usr/bin/env bash
read -p "Siapa nama kamu? " nama
echo "Senang bertemu, $nama!"
Behavior in the terminal
Siapa nama kamu? Arman
Senang bertemu, Arman!

-p displays the prompt text before reading input. Notice the space at the end of the prompt (? ) — without it, what the user types will "stick" directly to the end of the question mark. Small details like this make a script feel professional.

read -s — Silent Mode for Passwords

When a user types a password, we don't want the characters visible on screen (people behind could peek). The -s flag hides the input — but it's still stored in the variable:

read_s.sh — hidden password
#!/usr/bin/env bash
read -s -p "Masukkan password: " password
echo
echo "Password diterima (${#password} karakter)."
Behavior in the terminal
Masukkan password: ••••••••
Password diterima (8 karakter).

There are two important details here. First, -s doesn't display input — the cursor keeps blinking, and no keystroke appears. Second, note the empty echo on the line after read: because read -s doesn't produce a new line after the user presses Enter (similar to echo -n), without the extra echo, the next prompt would appear on the same line as the hidden input. That empty line "closes" the line.

Important

read -s hides the display, not secures the data. The password still sits in a variable (visible to other processes that can read memory) and can leak into shell history if you write it as an argument. read -s is not a substitute for proper secret management — it only prevents shoulder surfing. For real secrets, use tools like Vault, which we'll cover in another series.

read -t — Timeout: Don't Let a Script Wait Forever

In automation (cron jobs, CI/CD), a script that waits for input forever is a disaster — the pipeline hangs indefinitely. The -t flag limits the wait time in seconds:

read_t.sh — 5 second timeout
#!/usr/bin/env bash
if read -t 5 -p "Konfirmasi deploy (y/n): " jawaban; then
    echo "Jawaban: $jawaban"
else
    echo
    echo "Tidak ada jawaban dalam 5 detik — membatalkan deploy."
    exit 1
fi

What's interesting: read itself returns an exit status. If the user answers before the timeout, read succeeds (exit status 0); if the timeout runs out, read fails (non-zero exit status). That's why we can directly use if read ... as the condition — elegantly leveraging the $? lesson from episode 5. This pattern is ideal for automation scripts that must cancel themselves if no human is operating them.

read -n — Reading a Number of Characters (Single Key)

Sometimes we only need one character — like a "y" or "n" answer. The -n 1 flag makes read stop after one character, without waiting for Enter:

read_n.sh — single-key confirmation
#!/usr/bin/env bash
read -n 1 -p "Hapus cache? (y/N) " jawaban
echo
case "$jawaban" in
    y|Y) echo "Menghapus cache...";;
    *)   echo "Dibatalkan.";;
esac
Behavior in the terminal
Hapus cache? (y/N) y
Menghapus cache...

Note the (y/N) pattern with a capital N — a Unix convention meaning "the default is No". The user just presses one key, no Enter needed. It feels light and fast, exactly like how installer applications interact with users.

read -a — Reading Into an Array

The -a flag reads the entire input line and splits it into array elements by spaces (IFS). Very useful for accepting a list of values in one prompt:

read_a.sh — reading a list into an array
#!/usr/bin/env bash
read -a packages -p "Daftar paket (pisahkan spasi): "
for pkg in "${packages[@]}"; do
    echo "  - $pkg"
done
Behavior in the terminal
Daftar paket (pisahkan spasi): nginx redis postgres
  - nginx
  - redis
  - postgres

We'll cover arrays in depth in the indexed & associative arrays episode. For now, just understand that read -a nama_array is a short way to turn one input line into a list of loopable elements.

Concise read Flags Table

FlagFunctionWhen to use
-p "text"Display a prompt before readingAlmost always — the user must know what's being asked
-sDon't display input (silent mode)Passwords, tokens, sensitive input
-t NTimeout after N seconds; read fails if it expiresAutomation scripts that must cancel themselves
-n NStop after N characters (no Enter)Single-key y/n confirmation
-a arrStore input as an array (split on spaces)Accepting a list of values in one prompt
-rDon't treat \ as an escapeAlmost always — prevents backslashes from disappearing

Tip

Get used to always writing read -r alongside the other flags (e.g. read -rp "Nama: " nama). Without -r, bash treats backslash as an escape character — so a Windows path like C:\Users\Arman will lose \U, \A. Rule of thumb: if you don't know why you need the -r flag, you still need -r.

Input Validation Patterns: Trust Nothing the User Types

The first law of interactive programming: input from users can't be trusted. A user can press Enter without typing anything, type a number when text was requested, or type a wrong format. A good script validates, and if the input is bad, asks again — rather than continuing with wrong data.

1. Non-Empty Validation + Re-prompt

The most basic pattern: loop until the user gives a non-empty answer. The key is the loop — validating once isn't enough, because a user can give bad input many times.

validasi_tidak_kosong.sh
#!/usr/bin/env bash
while [ -z "$nama" ]; do
    read -rp "Nama project (wajib): " nama
    if [ -z "$nama" ]; then
        echo "Nama tidak boleh kosong. Coba lagi."
    fi
done
echo "Project: $nama"

The loop keeps running as long as $nama is empty. -z "$nama" means "true if the string is empty" — we'll study this test operator in more depth in episode 9. For now, understand the pattern: check → if wrong, tell the user → repeat. This is the universal validation cycle.

2. Format Validation

Empty validation only filters out missing input. To ensure the shape, use a regex pattern — bash has the =~ operator, which we briefly introduce here:

validasi_format.sh — only accepts numbers
#!/usr/bin/env bash
while ! [[ "$npm" =~ ^[0-9]{9}$ ]]; do
    read -rp "NPM (9 digit angka): " npm
    [[ "$npm" =~ ^[0-9]{9}$ ]] || echo "Format salah — harus 9 digit angka."
done
echo "NPM valid: $npm"

The pattern ^[0-9]{9}$ reads: start from the beginning, nine digits, then end — meaning the whole string must be exactly 9 digits, no more no less. In episode 9 we'll dissect [[ ]] and regex in more detail. What matters now: format validation is the second line of defense after the empty check.

3. Password Confirmation Pattern

For passwords, the standard practice is to enter twice and compare — preventing typos that are invisible because the input is hidden. Combine it with -s and a loop:

konfirmasi_password.sh
#!/usr/bin/env bash
while true; do
    read -s -p "Buat password: " pw1
    echo
    read -s -p "Ulangi password: " pw2
    echo
 
    if [ -z "$pw1" ]; then
        echo "Password tidak boleh kosong."
    elif [ "$pw1" != "$pw2" ]; then
        echo "Password tidak cocok — coba lagi."
    else
        break
    fi
done
echo "Password berhasil disimpan."

Why double confirmation? Because on a silent screen, typos are invisible — a user can be "sure" they typed correctly when they didn't. The second confirmation is a cheap safety net that saves you from big problems later (for example, a locked account because of a wrong password at deploy time).

Interactive Menus with select

Building a menu with read + case (like in the -n 1 example above) is possible, but bash provides a more convenient tool: the select loop. It automatically displays a numbered menu, accepts a choice, and stores it in a variable. Perfect for scripts offering a limited set of choices — exactly like a self-service kiosk menu.

menu_select.sh — menu with select
#!/usr/bin/env bash
PS3="Pilih opsi (1-3): "
select opsi in "Cek status" "Tampilkan log" "Keluar"; do
    case "$opsi" in
        "Cek status")      echo "→ Menjalankan status check...";;
        "Tampilkan log")   echo "→ Menampilkan log terakhir...";;
        "Keluar")          echo "Sampai jumpa!"; break;;
        *)                 echo "Opsi tidak valid.";;
    esac
done
Behavior in the terminal
1) Cek status
2) Tampilkan log
3) Keluar
Pilih opsi (1-3): 2
→ Menampilkan log terakhir...
Pilih opsi (1-3): 3
Sampai jumpa!

Two things to note:

  1. PS3 is select's special prompt. Without setting PS3, the default prompt is #? , which confuses users. Rule of thumb: always set PS3 — this is the most common question in select scripts.
  2. The select loop keeps showing the menu again after every choice. To exit, we need break (or exit). Without break, the user is stuck in the menu forever.

Warning

Categorically reject invalid menu input. select accepts a number choice, not text — if the user types a letter, $opsi becomes empty and case falls into the * branch. Always make sure there's a *) branch as a safety net, exactly like the example above. A menu without a * branch will stay "silent" with no explanation when the user types a number outside the range — a confusing behavior.

Practice: A Complete Maintenance Menu Script

Let's combine everything into one realistic script: a maintenance menu that accepts a password with confirmation, validates input, and offers different actions:

maintenance.sh — combining all patterns
#!/usr/bin/env bash
set -euo pipefail
 
# 1. Password confirmation (twice, matching, non-empty)
while true; do
    read -s -p "Buat password maintenance: " pw1; echo
    read -s -p "Ulangi password: " pw2; echo
    if [ -z "$pw1" ]; then
        echo "Password tidak boleh kosong."
    elif [ "$pw1" != "$pw2" ]; then
        echo "Tidak cocok — coba lagi."
    else
        echo "Password diterima."
        break
    fi
done
 
# 2. Ask for the target name with validation
while [ -z "$server" ]; do
    read -rp "Nama server target: " server
done
 
# 3. Action menu
PS3="Aksi untuk $server: "
select aksi in "Status" "Restart" "Keluar"; do
    case "$aksi" in
        "Status")  echo "→ Mengecek status $server...";;
        "Restart") echo "→ Me-restart $server...";;
        "Keluar")  echo "Selesai."; break;;
        *)         echo "Pilihan tidak valid.";;
    esac
done
Interactive menu script with password confirmation and validation

Let's break down the flow — a real example of how the three patterns work together:

StagePatternReason
1Password confirmation (read -s twice + compare)Prevents typos on hidden input
2Non-empty validation (while [ -z ] + re-prompt)The server name must not be empty
3select menu + PS3 + * branch + breakSafe action navigation that can exit

This script can now be run in the terminal with no arguments — all data is obtained through dialog. Compare it with the episode 5 script, which got everything through arguments. Both are valid — which one you use depends on context: arguments for data known in advance and automatable; read/select for data only a human can provide while interacting.

Common Pitfalls: Interactive Input Traps

1. read inside a pipe → variable lost

This is the most classic and confusing trap:

echo "Arman" | read nama
echo "Halo, $nama"   # KOSONG!

Why does it happen? Each side of a | pipeline runs in a subshell — a separate process. read reads the input in that subshell, and its variable dies with the subshell when the pipeline finishes. The nama variable in the main shell is never touched. The solution: read directly from a file (< file), use process substitution (< <(cmd)), or — if possible — read with a while loop in the same construct. As long as you write ... | while read, remember: the variables inside that loop won't escape it.

2. Forgetting the timeout in automation scripts

In cron jobs or CI/CD, no human is waiting at the terminal. If a script calls read without -t, and stdin doesn't provide data, the script will hang forever and the pipeline stops entirely. Always ask: will this script run without a human? If so, use read -t N and prepare a default path (like the read -t example above, which cancels the deploy).

3. PS3 not set → #? prompt

A select that doesn't set PS3 displays the mysterious #? . The user has no idea what to type. Set PS3 every time you use select.

4. Forgetting echo after read -s

read -s doesn't print a new line after Enter. The next prompt will stick to the same line as the hidden input — looking like the script "hung". Add an echo after it.

5. Comparing input without "..."

[ "$pw1" != "$pw2" ] with quotes is correct. Without quotes — [ $pw1 != $pw2 ] — a password containing spaces will split into several words and error with "unary operator expected". Always quote. This detail is covered thoroughly in episode 9.

Conclusion

In episode 6, we transformed scripts from mute tools into tools that can hold a dialogue: the read command with the -p (prompt), -s (silent for passwords), -t (timeout), -n (character count), and -a (array) flags; input validation patterns that are immune to empty and wrongly formatted input through the re-prompt cycle; the double password confirmation pattern; and interactive menus with select and PS3.

The takeaways:

  • Use read -rp for a clear prompt, and always -r to prevent backslashes from disappearing.
  • read -s for passwords — but remember, it hides the display, not secures the data.
  • read -t is mandatory in automation scripts so they don't hang.
  • Validation = loop check → tell the user → repeat; never trust raw input.
  • select + PS3 + * branch + break is the recipe for a safe menu.
  • Avoid | while read — variables won't escape the subshell.

Now that you can accept data from the command line and from interactive users, there's one more data domain that's often forgotten: the files on the filesystem. How do you grab a list of files matching a certain pattern — *.log, report_2026*, all .png files? In episode 7, we'll cover Globbing & Pathname Expansion — the *, ?, [...] wildcards, brace expansion, globstar **, plus why we must always use globs and never parse ls output. See you there!

Learn BASH Scripting - Interactive User Input & Menus with read | Learn BASH Scripting