Writing interactive shell scripts on Linux is a skill that can take simple automation and turn it into something truly valuable. Interactive shell scripting allows you to create scripts that can ask for user input, display menus, prompt for confirmation before performing dangerous actions, or even securely accept passwords without hardcoding them into the script. This guide covers everything from the basics of the read command to creating full-fledged terminal menu-style interfaces using whiptail.
Why Interactive Scripts Matter:
Most beginner bash scripts break in real environments because they assume everything is known upfront. The reality is different. Interactive scripts solve real problems like:
- Asking the operator which service to restart before doing it
- Prompting for a password without echoing it to the terminal
- Letting a user pick from a menu instead of editing variables inside the script
- Preventing accidental deletions with a yes/no confirmation gate
Once you learn this, you will stop writing single-use scripts and start building tools people actually want to run.
Before diving into interactivity, make sure you are comfortable with how bash scripts are structured. If you are just getting started, the guide on what is bash scripting in Linux is a solid starting point that covers the basics of how scripts work before you add user input into the mix.
Interactive vs Non-Interactive Shell: What Is the Difference?
This is one of the most searched questions around this topic and most articles give a half answer. So here is the full picture.
An interactive shell is one that talks back and forth with a user. When you open a terminal on Ubuntu or Rocky Linux and type commands, that is an interactive session. The shell shows you a prompt ($ or #), reads what you type, runs it, and shows output. Startup files like ~/.bashrc are loaded. The $PS1 variable is set because there is a prompt to display.
A non-interactive shell does not talk to anyone. When a cron job runs a script at 2am, or you pipe commands through bash -c, no human is sitting there. The .bashrc file is skipped. There is no prompt. The script just runs from top to bottom.
An interactive shell script is something in between. The shell itself is non-interactive (it is running a file), but the script is designed to pause and ask the user for input using commands like read. This is the kind of script this article teaches you to build.
How to Check Which Shell Type You Are In:
Run this one-liner in your terminal to check:
If you paste that into a running script file and execute it, it will say Non-interactive, because scripts run as non-interactive shells even when they contain read commands. The script interacts with a user, but the shell process itself is not interactive. That distinction matters when you are debugging why some scripts behave differently in cron versus the terminal.
Common Mistake:
Many beginners add read commands to cron jobs expecting them to wait for user input. A cron job has no terminal attached. The read command will immediately receive EOF and assign an empty value, causing the script to behave unexpectedly or fail silently.
Fix: Never use interactive prompts inside cron jobs. Use command-line arguments or config files for non-interactive automation instead. See the cron command guide for how to pass variables safely to scheduled scripts.
The read Command: How to Write Interactive Shell Scripts in Linux
The read command is the foundation of interactive shell scripting in Linux. Every other tool builds on top of what read does. Most tutorials show you only the basic usage. This section covers every useful flag with a real example for each.
The basic syntax is:
-p : Inline prompt text
Instead of using a separate echo before every read, the -p flag lets you add a prompt inline. Cleaner and one line shorter.
echo "Hello, $username"
Hello, john
-s : Silent input (passwords)
The -s flag suppresses what the user types. Nothing appears on screen as they type. This is how you collect passwords in shell scripts without showing them in plaintext.
echo ""
echo "Password stored (not shown)."
Note:
After using -s, always print an empty echo "" on the next line. Otherwise the next output appears on the same line as the password prompt which looks broken in the terminal.
Also add this trap before any read -s call so the terminal is restored if the script is force-killed mid-input:
-t : Timeout in seconds
If you want the script to move on automatically after a few seconds if no input is given, use -t. Very useful in automated deployment scripts that need a confirmation window but should not hang forever.
echo "You entered: $answer"
else
echo ""
echo "Timeout reached. Continuing with defaults."
fi
-n : Limit character input
With -n NUM, read stops after exactly NUM characters. No need to press Enter. Good for single-key yes/no prompts.
echo ""
echo "Key pressed: $key"
-r : Raw mode (no backslash escaping)
By default, read treats backslash as an escape character. If you are collecting file paths or regex patterns, always use -r to get the literal string.
echo "Path: $filepath"
-a : Read into an array
When a user enters space-separated values, -a splits them into array elements automatically.
for s in "${servers[@]}"; do
echo "Processing: $s"
done
Processing: web01
Processing: db01
Processing: cache01
Pro Tip:
Combine flags freely. read -r -s -p "Enter API key: " apikey gives you a raw, silent, prompted read in one line. The flags are additive. For more practical examples of how echo and read work together in scripts, check out the guide on using echo effectively in shell scripts.
Yes/No Prompts and Input Validation Patterns
One of the most practical things in interactive bash scripting is asking the user to confirm a dangerous action before executing it. A yes/no prompt done right should handle uppercase, lowercase, and invalid input without crashing.
Reusable confirm() function for production scripts
confirm() {
while true; do
read -r -p "$1 [y/n]: " choice
case "$choice" in
y|Y|yes|YES) return 0 ;;
n|N|no|NO) return 1 ;;
*) echo "Please enter y or n." ;;
esac
done
}
if confirm "Delete all logs in /var/log/app?"; then
echo "Deleting logs..."
# rm -rf /var/log/app/*.log
else
echo "Cancelled. No files deleted."
fi
This function loops until valid input is given. You can reuse it anywhere in the script just by calling confirm "Your question here".
Numeric input validation
read -r -p "Enter a number between 1 and 10: " num
if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le 10 ]; then
echo "Valid input: $num"
break
else
echo "Invalid. Please enter a number from 1 to 10."
fi
done
Empty input guard
read -r -p "Enter a hostname (required): " host
if [ -z "$host" ]; then
echo "Hostname cannot be empty. Try again."
else
break
fi
done
echo "Hostname set to: $host"
Common Mistake:
Forgetting to quote variables in conditions causes word-splitting bugs. if [ $var == "yes" ] will throw an error if $var is empty because the shell sees if [ == "yes" ].
Fix: Always quote: if [ "$var" == "yes" ]. This pattern is covered thoroughly in the bash exit codes and error handling guide.
Bash select Statement: Building Terminal Menus
The select statement is the cleanest way to build numbered menus in bash. It handles all the display and loop logic for you. Most articles show only the surface-level example. Here is a proper deep-dive with real patterns you can use.
Basic select menu
PS3="Choose an action: "
options=("Start Service" "Stop Service" "Check Status" "Exit")
select opt in "${options[@]}"; do
case $opt in
"Start Service") echo "Starting service..."; break ;;
"Stop Service") echo "Stopping service..."; break ;;
"Check Status") echo "Checking status..."; break ;;
"Exit") echo "Goodbye."; break ;;
*) echo "Invalid option. Try again." ;;
esac
done
2) Stop Service
3) Check Status
4) Exit
Choose an action: 1
Starting service...
Dynamic option list from an array
Instead of hardcoding menu items, you can build the list from a directory listing, a file, or a command output.
Note:
The mapfile command requires Bash 4.0 or higher. Most Linux distros ship with Bash 4+ so this is fine on Ubuntu, Rocky Linux, and RHEL. However macOS ships with Bash 3.x by default and mapfile will not work there without upgrading Bash first.
PS3="Select a config file to edit: "
mapfile -t configs < <(ls /etc/*.conf 2>/dev/null)
options=("${configs[@]}" "Cancel")
select choice in "${options[@]}"; do
if [ "$choice" = "Cancel" ]; then
echo "Cancelled."
break
elif [[ -n "$choice" ]]; then
echo "Opening $choice..."
# vi "$choice"
break
else
echo "Invalid selection."
fi
done
Looping menu (return after each action)
Remove the break from all non-exit branches and the menu keeps showing after every selection until the user explicitly chooses to quit. This is useful for server maintenance scripts where the operator might need to run several actions in sequence.
PS3="Server Menu > "
while true; do
select action in "Disk Usage" "Memory Info" "Running Processes" "Quit"; do
case $action in
"Disk Usage") df -h; break ;;
"Memory Info") free -h; break ;;
"Running Processes") ps aux | head -20; break ;;
"Quit") echo "Exiting."; exit 0 ;;
*) echo "Invalid." ;;
esac
done
done
Note on PS3:
PS3 is a special bash variable that sets the prompt text for select menus. The default is #? which looks confusing to users who are not developers. Always set PS3 to something readable. For related shell prompt variables, the shell scripting environment setup guide has a good breakdown of PS1, PS2, PS3, and PS4.
whiptail and dialog: GUI-Style Menus in the Terminal
This is the section nobody else covers. If you want your interactive shell script to look more polished, with real dialog boxes, checkboxes, and radio buttons right inside the terminal, whiptail and dialog are what you need.
Installing whiptail and dialog
On Ubuntu or Debian:
On Rocky Linux, RHEL, or CentOS Stream:
Note:
On Rocky Linux and RHEL, whiptail comes from the newt package. The command is still called whiptail after install. If you are setting up Rocky Linux for the first time, the Rocky Linux 8 installation guide covers initial package setup including enabling repos.
Message box (msgbox)
Input box
echo "Hello, $NAME"
The 3>&1 1>&2 2>&3 Pattern:
whiptail writes the user's choice to stderr, not stdout. So to capture it in a variable, you have to swap file descriptors: save stdout to fd3, redirect stdout to stderr, redirect stderr (which has the result) back to stdout. It looks strange but this is the standard pattern for all whiptail value captures.
Yes/No dialog box
echo "Rebooting..."
# sudo reboot
else
echo "Reboot cancelled."
fi
Checklist (multi-select)
"nginx" "Web server" OFF \
"mariadb" "Database" OFF \
"php" "PHP runtime" OFF \
"redis" "Cache layer" OFF \
3>&1 1>&2 2>&3)
echo "Installing: $PACKAGES"
Radio list (single select)
"dev" "Development" ON \
"stag" "Staging" OFF \
"prod" "Production" OFF \
3>&1 1>&2 2>&3)
echo "Deploying to: $ENV"
Tip: whiptail vs dialog:
whiptail is lighter, has no color themes, and is available by default on most Debian/Ubuntu systems. dialog supports more widget types (calendar, file browser, progress bar) and allows color customization. For basic scripts on enterprise Linux, stick with whiptail. For more complex installer-style UIs, use dialog. Both use the same syntax structure.
Real-World Interactive Script Examples
Theory is only useful when you can see it in action. Here are three production-ready scripts that combine everything covered above.
Script 1: Server Maintenance Menu
This script presents a looping menu to a sysadmin. They can restart, stop, or check the status of nginx without running individual commands manually.
# server-menu.sh - Interactive service management
SERVICE="nginx"
PS3="Choose action for $SERVICE: "
while true; do
echo ""
select action in "Start" "Stop" "Restart" "Status" "Quit"; do
case $action in
"Start") sudo systemctl start $SERVICE; echo "$SERVICE started."; break ;;
"Stop") sudo systemctl stop $SERVICE; echo "$SERVICE stopped."; break ;;
"Restart") sudo systemctl restart $SERVICE; echo "$SERVICE restarted."; break ;;
"Status") sudo systemctl status $SERVICE; break ;;
"Quit") echo "Exiting menu."; exit 0 ;;
*) echo "Invalid choice." ;;
esac
done
done
Script 2: Backup Confirmation Wizard
Before running a backup job, this script asks for confirmation and lets the user choose a destination directory. It guards against empty input and confirms the final action.
# backup-wizard.sh
echo "=== Backup Wizard ==="
while true; do
read -r -p "Enter source directory to backup: " SRC
[ -n "$SRC" ] && break
echo "Source cannot be empty."
done
while true; do
read -r -p "Enter backup destination path: " DEST
[ -n "$DEST" ] && break
echo "Destination cannot be empty."
done
echo ""
echo "Summary:"
echo " Source : $SRC"
echo " Dest : $DEST"
echo ""
read -r -p "Proceed with backup? [y/n]: " confirm
case $confirm in
y|Y)
mkdir -p "$DEST"
cp -r "$SRC" "$DEST" && echo "Backup complete." || echo "Backup failed."
;;
*)
echo "Backup cancelled."
;;
esac
For a deeper look at Linux backup strategies that you can wrap with scripts like this, the Linux server backup solutions guide covers rsync, tar-based, and cloud backup approaches in detail.
Script 3: New Linux User Creation with Prompts
This script collects a username, sets a password silently, and creates the user with one confirmation step.
# create-user.sh - Interactive user creation
echo "=== New User Setup ==="
while true; do
read -r -p "Enter new username: " NEWUSER
if id "$NEWUSER" >/dev/null 2>&1; then
echo "User $NEWUSER already exists. Try another."
elif [ -z "$NEWUSER" ]; then
echo "Username cannot be empty."
else
break
fi
done
while true; do
read -s -p "Set password for $NEWUSER: " NEWPASS
echo ""
read -s -p "Confirm password: " NEWPASS2
echo ""
if [ "$NEWPASS" = "$NEWPASS2" ]; then
break
else
echo "Passwords do not match. Try again."
fi
done
read -r -p "Create user $NEWUSER? [y/n]: " confirm
if [ "$confirm" = "y" ] || [ "$confirm" = "Y" ]; then
sudo useradd -m "$NEWUSER"
printf "%s:%s\n" "$NEWUSER" "$NEWPASS" | sudo chpasswd
echo "User $NEWUSER created successfully."
else
echo "Cancelled."
fi
Troubleshooting Interactive Shell Scripts
Interactive scripts fail in ways that are not always obvious. These are the most common issues and how to fix them properly.
Problem 1: read not waiting for input
If your script's read command exits immediately without waiting, you are likely running the script through something that closes stdin, like a pipe or process substitution.
cat file.txt | while read line; do
read -p "Continue? " ans # This wont wait
done
# Correct - redirect file to loop, keep stdin for read
while read -r line; do
read -r -p "Continue? " ans < /dev/tty
done < file.txt
Using < /dev/tty forces read to take input from the actual terminal even when stdin is redirected.
Problem 2: select loop not exiting
If your select menu keeps looping after a valid selection, you forgot to add break inside the matching case branch. Every valid action branch needs a break to exit the select loop. The loop only exits with an explicit break or exit.
Problem 3: Script behaves differently under sudo
When you run a script with sudo, it runs as root in a fresh environment. Variables from your user environment, including PATH entries and exported variables, may not be available. Always use full paths in scripts that will run as root:
nginx -t
# Use full path:
/usr/sbin/nginx -t
The guide on configuring sudo in Linux explains how to set up sudo rules so scripts get the right environment when elevated privileges are needed.
Problem 4: whiptail not found error
if ! command -v whiptail >/dev/null 2>&1; then
echo "whiptail not found. Installing..."
# Ubuntu/Debian:
sudo apt install whiptail -y
# Rocky/RHEL:
# sudo dnf install newt -y
fi
Problem 5: Script behaves differently in cron
Cron has no terminal. Any read prompt in a cron script will get EOF immediately and the variable will be empty. If your script has interactive prompts but also needs to support scheduled runs, add a flag check at the top:
# Run interactively: ./script.sh
# Run from cron: ./script.sh --auto
AUTO=false
[ "$1" = "--auto" ] && AUTO=true
if [ "$AUTO" = false ]; then
read -r -p "Enter target server: " SERVER
else
SERVER="default-server"
fi
echo "Connecting to $SERVER..."
Common Mistake:
Running a script with bash script.sh instead of ./script.sh can ignore the shebang line. If your script relies on bash-specific features like select, always execute with bash script.sh or make it executable with chmod +x and run it directly. The bash hello world beginner guide covers shebang lines and execution modes properly.
Best Practices: When to Use read vs select vs whiptail
Picking the right tool matters. Using whiptail for a one-line confirmation is overkill. Using plain read when you have 10 options makes the script hard to use. Here is a simple decision guide:
Tool Selection Guide:
Use read when: you need freeform text input, a password, a file path, or a simple yes/no answer. It is POSIX-compatible and works everywhere.
Use select when: you have 3 to 8 fixed options and want a clean numbered menu. Pure bash, no dependencies, works over SSH and in minimal containers.
Use whiptail when: you are building an interactive installer or maintenance tool where visual polish matters. Requires the newt library. Does not work in non-terminal environments (cron, CI/CD pipelines).
Additional best practices for production-ready interactive scripts:
- Always add
set -eat the top so the script exits on any error rather than continuing with bad data - Trap Ctrl+C cleanly with
trap 'echo "Cancelled."; exit 1' INTto avoid leaving things in a half-done state - Log what the user chose: append to a log file so there is an audit trail of who ran what interactively
- Test your script with an empty terminal (SSH session with minimal env) before deploying it to servers
- Keep interactive prompts near the top of long scripts so all input is collected before heavy operations begin
For more automation patterns that combine bash scripting with real-world Linux workflows, the Linux bash scripting automation guide for 2026 covers scheduled tasks, logging, and deployment script patterns.
Frequently Asked Questions
What is the difference between an interactive and a non-interactive shell script?
An interactive shell is a live session where you type commands and see results. A non-interactive shell is what runs when you execute a script file. The shell process is non-interactive, but a script can still be interactive if it uses read to collect user input during execution. The key difference is that cron jobs, pipes, and process substitutions are fully non-interactive and will not pause for user input at all. You can detect the shell mode inside a bash script with:
How do I validate user input in a bash script?
The cleanest approach is to wrap your read in a while true loop and only break when the input passes your validation check. For numeric ranges, use regex to confirm digits and then compare. For empty input, use [ -z "$var" ]. For email or path patterns, test with [[ "$var" =~ PATTERN ]]. Always quote your variables and use -r with read to avoid backslash surprises. See Section #03 in this article for copy-paste validation patterns including numeric guards, empty checks, and retry loops. The shell scripting interview questions guide also has validation-related questions that test this knowledge.
Can I use whiptail on Rocky Linux or RHEL?
Yes. On Rocky Linux and RHEL, whiptail is provided by the newt package. Install it with:
After install, the whiptail command is available. All the examples in Section #05 work identically on Rocky Linux 8/9 and RHEL 8/9. On Ubuntu and Debian, the package name is simply whiptail.
Why does my select menu keep repeating after I choose an option?
The select loop continues until it sees a break or exit statement. If your case branch does not include break, the loop restarts. The fix is to add break at the end of each valid action branch. If you want the menu to loop intentionally and only stop on a quit option, add break only to the quit branch and wrap the select in a while true loop. Section #04 in this article shows both patterns with working code.
How do I collect a password in a bash script without showing it on screen?
Use the -s flag with read. This suppresses terminal echo so nothing appears as the user types. Always follow it with an empty echo to move the cursor to the next line:
echo ""
Never store passwords in plaintext script files or print them with echo $PASS. For Linux security hardening that includes credential handling in scripts, see the Linux server hardening checklist.
Is whiptail available in minimal Docker or container environments?
Not by default. Most minimal container images do not include whiptail or even a proper terminal. More importantly, whiptail requires a terminal (TTY) to render its interface. If your script runs inside a container without an attached TTY, whiptail will fail. In that case, fall back to plain read prompts or pass all values as environment variables or arguments. Use docker run -it if you need a TTY attached to the container session.
What is the bash select statement and how is it different from read?
read takes freeform text input. The user types whatever they want and the script stores it in a variable. select generates a numbered list from an array and forces the user to pick one of the listed options by number. If the user enters an invalid number, select sets the variable to empty and loops again. select is better when you have a fixed set of choices. read is better when the user needs to enter a custom value like a file path or hostname.
Summary
Learning how to write interactive shell scripts in Linux opens up a whole category of tools you simply cannot build with static scripts. Starting from the full set of read flags, through validated prompts and select menus, all the way to proper TUI dialog boxes with whiptail, this guide has covered every layer of interactivity that matters for real sysadmin and DevOps work.
The troubleshooting section and real-world script examples are there so you can adapt these patterns directly to your environment without starting from scratch. For a deeper dive into where interactive scripts fit in a broader automation strategy, the Linux shell scripting command cheat sheet is a good quick reference to keep open while building.
Related Articles
- What Is Bash Scripting in Linux? A Beginner's Introduction (Part 1 of 34)
- Bash Script Exit Codes and Error Handling Explained (Part 5 of 34)
- Linux Shell Scripting Interview Questions and Answers
- Linux Shell Scripting Command Cheat Sheet
- Linux Bash Scripting and Automation Guide 2026
- script - Linux manual page
Learn step-by-step how to automate Linux tasks with real-world scripts and practical examples.