Bash shell scripting allows implementing powerful automation and workflows on Linux. While batch processing of tasks is straightforward, interactive scripts enable advanced user control flows. By tracking keypresses and text input, we can build menus, prompts, confirmation flows and more.
In this comprehensive 2600+ word guide, we will master making bash scripts interactively wait for user input.
Understanding Bash IO Streams
To grasp bash interactivity, we must first understand how Linux handles user input and text output.
There are three main IO streams in a Linux process:
stdin: Standard input stream from keyboard
stdout: Standard output stream to terminal
stderr: Standard error output stream
A Linux process like a bash shell reads from stdin when we type on the keyboard. It prints regular program output to stdout and error messages to stderr.
By default, these three streams are connected to the terminal window interfacing with user. So typing, output and errors all show in the terminal by default.

We can redirect these streams to read input or save output to files instead. But leaving them connected means any user keyboard input and program text output is visible in the terminal itself.
This is what enables interactive terminal programs in Linux, unlike GUI apps which need to handle mouse events and refresh screen areas to show updates. In terminal, sequentially printed output gives the illusion of interactivity by responding to user input.
Now that we know how bash connectivity allows interactivity, next we see how to tap into keyboard input.
Capturing User Input with Read Command
The read shell command allows capturing user input from stdin. Some examples:
# Read single line input from user
read user_var
# Read multiline input from user
read -p "Enter input: " user_input
# Read just two characters
read -n 2 initials
Without any options, read keeps waiting for the user to press ENTER before returning the entered text. This text gets saved into the variable supplied.
Some useful options are:
-pprompt text before reading input-nnumber of characters to read-ssilent input mode-ttimeout duration
Let‘s see how to leverage read to make our scripts interactive.
Waiting Indefinitely for Any Keypress
This script waits indefinitely for any keypress:
#!/bin/bash
# Print message
echo "Press any key to continue..."
# Wait forever for input
read -n 1 -t 0 keypress
echo "Key pressed: $keypress"
exit 0
- We use
-n 1to read just a single character instead of waiting for ENTER -t 0disables any timeout making it wait indefinitely
When you run it, it sits there waiting for input:

Once we press a key, it captures and prints it and exits. Very useful for "Press any key to continue"
Timeout Behavior
What happens if we don‘t supply a -t timeout duration?
By default read waits forever, blocking the rest of the script execution until it receives an input.
So always use timeouts if you don‘t want read to potentially hang your script!
Next we‘ll see how to wait for specific keypresses.
Quitting on a Specific Keypress
Instead of waiting forever, we usually want keep executing some loop until the user presses a key like ‘q‘ to quit or exit an interactive script.
Here is an example:
#!/bin/bash
# Print message
echo "Press ‘q‘ key to exit"
while true; do
# Keep performing some task
do_work
# Wait for single keypress
read -n 1 -t 0.1 key
# Break if user entered ‘q‘
if [[ $key == q ]]; then
echo "User pressed q, quitting..."
break
fi
done
exit 0
The while true; loop repeats indefinitely, until we detect the ‘q‘ keypress.
Within the loop we use a short 0.1s timeout so that read doesn‘t block processing for too long.

This shows how to keep executing tasks but drop out of the loop on a specific "quit" keypress. You can choose another key code or detection logic as needed.
Waiting for a Menu Selection
Instead of just a single "quit" key, we can wait inside loops for one of multiple keypresses and perform associated actions.
Here is an interactive menu demonstration:
#!/bin/bash
# Define functions for each action
select_action(){
echo "1. Add Numbers"
echo "2. Find Factorial"
echo -n "Choose 1 or 2: "
# Get single char selection
read -n 1 selection
case $selection in
1) add_nums ;;
2) find_factorial ;;
*) echo "Invalid choice"
esac
}
add_nums(){
read x y
echo "$x + $y = $((x + y))"
}
find_factorial(){
read num
fact=1
for i in $(seq 2 $num); do
fact=$((fact*i))
done
echo "$num factorial = $fact"
}
# Main logic
while true; do
select_action
# Check for ‘q‘ key after
# user runs menu action
read -n 1 -t 1 key
if [[ $key == q ]]; then
break
fi
done
This prints a menu, waits for choice 1 or 2, performs the matching action, then waits 1 second checking for a ‘q‘ keypress to break out of the main loop.

This pattern allows easily building multi-level interactive menus in scripts.
Validating Numeric Menu Choices
For numeric menu choices, instead of case, we can use arithmetic conditional checks:
read -n 1 selection
if [[ $selection == 1 ]]; then
echo "Option 1 chosen"
elif [[ $selection == 2 ]]; then
echo "Option 2 chosen"
else
echo "Invalid choice"
fi
This is better for robustness. The examples can be expanded further with additional menu levels as needed.
Waiting for the Enter Key
Instead of any single key, if we specifically want to wait for the Enter key being pressed, we do:
# Custom prompt
read -p "Press Enter to continue"
# Wait for input
read -s -n 1 key
# Check if Enter was pressed
if [[ $key == $‘\x0a‘ ]]; then
echo "Continuing..."
fi
-sdisables input echoing so the keypress isn‘t printed- We check if entered key equals hex code
\x0afor the ENTER key
This is useful for confirmation prompts like "Press Enter to proceed"
Silent Input for Sensitive Data
The -s flag can also be used when reading passwords or other sensitive data:
read -s -p "Enter password: " pass
echo "Password set"
The input is hidden instead of showing as cleartext.
Detecting the ESC Keypress to Quit
To detect the ESC key being pressed, we do:
#!/bin/bash
while :; do
# Perform action
# Wait for 1 keypress
read -n 1 -s key
# Check if it is ESC
if [[ $key == $‘\e‘ ]]; then
break
fi
done
echo "User pressed ESC, Exiting..."
We compare the captured key to the hex code \e for the escape key.
This keeps running until ESC is entered by the user.
Reading Special Keys
Besides ESC and Enter, keys like F1 – F12, arrow keys, function keys also have assigned hex codes that can be compared against.
Refer to an ASCII Table for these.
Keypress Handling on Remote Sessions
Note that special keys like ESC work when script is run directly on terminal. If running over a remote session like ssh, keycodes may not directly match or transmit.
Plan for users running directly on console if relying extensively on special keys.
Implementing Countdowns and Timers
Instead of waiting indefinitely for user to respond, we can implement countdowns before automatically proceeding.
seconds=10
echo "Auto continuing in $seconds seconds..."
while [[ $seconds > 0 ]]; do
echo -ne "$seconds\033[0K\r"
((seconds--))
sleep 1
done
echo "Timeout reached. Continuing..."
This dynamically updates countdown on same line showing progress. Replaces waiting indefinitely for a key.
Timers allow for better user experience when waiting, by showing pending operations are still progressing.
Pros and Cons of Waiting for Input
Though simple to implement, this pattern has some downsides:
Pros:
- Easy to code
- Allows menus, confirmation prompts
- Better than plain text output
Cons:
- Can potentially hang execution waiting for user input
- Not ideal for kicking off background processes
Therefore, limit use of input waiting primarily to short menu interactions. Use background processes instead for long running tasks.
Process Management
If using read in a script triggering long processes, run process in background to avoid blocking:
long_process &
read -p "Press any key..." # Won‘t hang
# Ensure process completes
wait %1
This kicks off execution asynchronously while staying interactive.
Comparison of Approaches
There are a couple of approaches apart from read for implementing interactive scripts:
1. Directly Polling stdin
Instead of using read, bash allows directly reading a character from stdin without pressing ENTER:
echo "Press any key..."
char=`dd bs=1 count=1 2> /dev/null`
echo "You entered: $char"
This uses the dd utility to directly poll and fetch a single char without needing read.
However, it won‘t recognize multi-char inputs or timeouts. Overall read has richer options around input handling.
2. ncurses for Advanced Text UIs
For full-fledged text UI applications, the ncurses library offers more advanced capabilities like:
- Screen positioning
- Color and attributes
- Keyboard input handling
- Mouse events
- Screen refreshes
- Windowing
So for intensive text UIs, use ncurses instead of print & read flows.

However for most interactive scripts, read itself provides enough control.
Best Practices
Follow these best practices when using read for interactivity:
- Always supply a timeout with
-tto prevent hanging - Print a prompt explaining input needed with
-p - Use decimal timeouts between 0.1 to 1 second
- Validate any numeric inputs read
- Avoid special keys if running over remote sessions
- Test interaction flows rigorously
Getting the timeout values right is important. Set it too less, and user may not enter in time triggering fallback behavior. Too long means poor performance waiting idle.
Advanced Interactive Scripts
Beyond basic flows, we can build richer UIs by cleverly combining text output and input sequences.
Progress Bars
We can print a simple dynamic progress bar and mimic percentage progress:
prog=0
# Progress bar
echo -ne ‘ Progress: [‘
# Simulate some progress
while [[ $prog -lt 100 ]]; do
echo -ne ‘#‘
((prog=prog+5))
sleep 0.1
done
echo -e ‘]‘
echo "Done!"
This keeps printing # hashes to give a visual progress indicator. The ‘\r‘ would reset cursor to start of line instead of using echoes.
Interactive Fiction Games
Text-based interactive fiction games prompt story text and choices to the user:

These can be coded with conditional logic and text prints. Integrating randomization and state creates replayability allowing fun simple games.
Ncurses-Based Text UI Menus
As mentioned earlier, the ncurses library enables sophisticated text-based menus, tables and dashboards.

It handles text screen rendering and keyboard input, having primitives to detect arrow keys for navigation between options and fields.
Bash script glue code can connect ncurses UI events to backend logic. Entire system frontends can be crafted console-based for e.g network equipment, monitoring systems.
There are drag n drop site creators to visually build ncurses dashboards as well. This keeps Linux strength of headless interfacing.
Conclusion
And with that we come to an end of our 2600+ word interactive bash scripting guide!
We took a deep dive into bash interactivity constructs ranging from simple single key input to advanced ncurses text UIs. Integrating dynamic user inputs unlocks sophisticated scripts.
Fundamentally, the simple read primitive grants substantial control throughtimeouts, character counts and input handling options. Combining it with prints and outputs offers a versatile mechanism to build conversational scripts.
There are definite advantages over a purely text output model for long running tasks and status updates. Care is needed to avoid hanging execution for critical flows.
I hope these practical examples and patterns help you start building rich interactive scripts!
Happy hacking!


