Reading Keyboard Input
5 min read
Core idea
read is the built-in that turns a script from a batch program into an interactive one. It pulls a single line from standard input, applies word-splitting using the IFS variable, and assigns the resulting fields to the variables you name. That single line is also the boundary at which untrusted data enters your program — so every read should be immediately paired with a validation step that decides whether the input is acceptable before the rest of the script ever sees it.
Author's framing: Often the difference between a well-written program and a poorly written one lies in the program's ability to deal with the unexpected. Frequently, the unexpected appears in the form of bad input.
read in one sentence
read [-options] [var1 [var2 ...]] — fewer fields than variables leaves the extras empty; more fields than variables packs the remainder into the last variable. If you give no variables, the entire line lands in REPLY. That's the whole model.
The options that matter most
| Option | What it does | When you reach for it |
| --- | --- | --- |
| -p "prompt" | Print the prompt on stderr before reading | Every interactive script |
| -s | Don't echo characters as typed | Passwords, secrets |
| -t N | Time out after N seconds | Defensive: don't hang forever |
| -n N | Read N characters, don't wait for newline | Single-keystroke menus |
| -r | Raw mode — don't treat \ as escape | Always; backslashes in real input are rarely escapes |
Of these, -r deserves to be a habit. Without it, a backslash in input gets interpreted, mangling filenames, Windows paths, and any data that originated outside your script. The shellcheck rule SC2162 flags every read without -r — that's how universal the recommendation is.
IFS controls how the line splits into fields
read first last age < /etc/passwd-like-file only fills three variables if the line has at least three whitespace-separated tokens. To split on : instead, set IFS=":" for the duration of the call: IFS=":" read user pwd uid gid name home shell <<< "$line". Putting the assignment in front of the command scopes it to just that command — the global IFS is undisturbed.
Pipelines run read in a subshell — and lose your variables
echo "value" | read x looks like it should set x to value. It doesn't. The right side of the pipe runs in a subshell, the assignment to x happens in that subshell's environment, and when the subshell exits, the assignment vanishes. The bash idiom to work around this is the here string — read x <<< "value" — which feeds the string into read without a subshell. Same effect; correct semantics.
Validation is a separate step — and it is not optional
read only gives you a string. Before treating that string as a number, a filename, a yes/no, or a menu choice, the script must check. The shape of the check is always the same: a regex (=~ inside [[ ]]) or a glob, run inside an if, returning an error message and an early exit on failure. The script after the validation can then assume the variable is well-formed.
Why it matters
The general rule — a program that accepts input must be able to deal with anything it receives — applies whether the input comes from a keyboard, a file, a network socket, or another script. read is most programmers' first exposure to that boundary in shell, and the habits formed here (-r, -p, read-then-validate) carry into every later script that consumes data.
It separates what from how
A well-structured interactive script has three layers: prompt-and-read, validate, act. Each layer is small and obvious. Mixing them — using the raw $REPLY in a case statement without a validation pass — turns the script into a series of special cases the writer can't keep straight.
It defends against the destructive edge case
The book's canonical horror story is cd "$dir_name" && rm *. If dir_name came from a read and was never validated, the user can supply an empty string, the cd does nothing, the rm deletes the current working directory's files. Validation — "does this name an existing directory?" — closes that hole before the dangerous code runs.
It makes secrets safer
A password read with read -s -p "Password: " pass doesn't echo, doesn't get logged in scrollback, and (paired with -t) doesn't leave the script hung at a prompt forever. Reading secrets without -s is the kind of thing that ends up in screenshots.
Key takeaways
Mental model
Practical application
-
Prompt with
-r -p. Always-r. The prompt goes to stderr automatically with-p, which means it survives output redirection cleanly. -
Capture into a named variable.
read -r -p "Name: " namereads better thanreadfollowed by uses of$REPLY. -
Validate shape with regex inside
[[ ]] =~. For numbers:^-?[0-9]+$. For yes/no: a glob is enough —case "$ans" in [yY]*) ... ;; esac. -
Validate range or membership after shape. A string that looks like a number can still be out of range; check
(( n >= 1 && n <= 100 ))next. -
On invalid input, write to stderr and either re-prompt or exit. Re-prompting in a
whileloop is the pattern for "keep asking until valid"; exiting is the pattern for single-shot scripts. -
For secrets, add
-s. For "do not hang", add-t. A login script withread -s -t 30 -p "Password: " pwis the safe shape. -
For piped input, use a here string, not a pipe.
read -r line <<< "$data"works;echo "$data" | read -r linedoesn't.
Example
You are writing a release script that needs to confirm "Are you sure you want to push v1.2.3 to production?" before doing anything irreversible. The wrong way is:
read -p "Confirm? " ans
[[ "$ans" == "yes" ]] && deploy
Three problems: no -r so a backslash mangles input; the case is hard-coded ("Yes" or "YES" silently fail); no validation step means typos slip through. The right shape is:
read -r -p "Type 'yes' to push v1.2.3 to production: " ans
case "$ans" in
yes) ;; # exact match only — keep going
*) echo "Aborted (got '$ans')." >&2; exit 1 ;; # any other input bails
esac
deploy v1.2.3
Now the script answers a strict question with a strict pattern. "yes" works. "Yes", "y", "YES", a stray return — all abort. The user has to commit to the dangerous action; the script will not interpret ambiguity as consent. That is what input validation buys you: the right to assume, on line deploy v1.2.3, that the human really meant it.
Related lessons
Related concepts
- Input Validationlinked concept
- Bash Scriptinglinked concept
- Control Flowlinked concept
- Defensive Programminglinked concept