read is Bash’s built-in for getting input, either typed by a user or read from a file or pipe. It’s how install scripts ask questions, how scripts confirm dangerous actions, and how loops process files line by line.

Basic prompt

read -rp "Domain name: " domain
echo "Configuring $domain"
  • -p "text" shows a prompt.
  • -r keeps backslashes literal. Always use -r unless you have a reason not to.

Without a variable name, the input goes into $REPLY.

Hidden input for passwords

read -rsp "Database password: " db_pass
echo           # move to a new line, since -s also hides the Enter

-s (silent) doesn’t echo what’s typed. Don’t pass passwords as command-line arguments. Other users can see those in ps.

Defaults

read -rp "Port [3000]: " port
port="${port:-3000}"

Or pre-fill an editable default with readline:

read -e -i "/var/www" -rp "Web root: " webroot

Yes/no confirmation

read -rp "Delete all backups older than 30 days? [y/N] " answer
if [[ "$answer" =~ ^[Yy]([Ee][Ss])?$ ]]; then
  find /var/backups -mtime +30 -delete
else
  echo "Aborted."
fi

A capital letter in [y/N] signals the default. Treating anything other than an explicit yes as “no” is the safe choice for destructive actions.

Read a single key without needing Enter:

read -n 1 -rp "Continue? (y/n) " key; echo

Timeouts

if read -t 10 -rp "Reboot now? [y/N] " ans; then
  [[ "$ans" == [Yy] ]] && reboot
else
  echo; echo "No answer, skipping."
fi

read -t returns a non-zero status when it times out. That’s useful for scripts that sometimes run unattended.

Several values at once

read -rp "First and last name: " first last

Words are split on $IFS (spaces, tabs, newlines). The last variable gets everything left over.

Split on a different delimiter:

IFS=: read -r user _ uid gid _ home shell <<< "$(getent passwd www-data)"
echo "$user has UID $uid and home $home"

IFS=, read -r name email role <<< "Alice,[email protected],admin"

<<< is a “here string”: it feeds a string as input.

Into an array:

read -ra parts <<< "web1 web2 web3"
echo "${parts[1]}"     # web2

Reading files line by line

while IFS= read -r line; do
  echo "→ $line"
done < domains.txt
  • IFS= preserves leading and trailing spaces.
  • -r preserves backslashes.
  • The < file redirect at the end keeps the loop in the current shell, so variables set inside it survive.

If the last line lacks a trailing newline, it gets skipped. Handle that with:

while IFS= read -r line || [[ -n "$line" ]]; do …; done < file

Skip comments and blank lines:

while IFS= read -r line; do
  [[ -z "$line" || "$line" == \#* ]] && continue
  …
done < config.txt

More loop patterns are in Bash for loops.

Scripts that are piped or run non-interactively

When a script runs from curl … | bash, cron or CI, stdin isn’t a keyboard:

  • In curl | bash, read would consume the script itself. Read from the terminal explicitly: read -rp "…" ans < /dev/tty.
  • In cron and CI there’s no terminal at all. Give every prompt a non-interactive way out: environment variables, flags like --yes, or a check with [[ -t 0 ]].
if [[ -t 0 ]]; then
  read -rp "Domain: " DOMAIN
fi
: "${DOMAIN:?set DOMAIN or run interactively}"

Input validation

Check the input before using it, especially when it ends up in commands or config files:

until [[ "$port" =~ ^[0-9]+$ ]] && (( port >= 1 && port <= 65535 )); do
  read -rp "Port (1-65535): " port
done

Quick reference

Task Command
Prompt read -rp "Q: " var
Hidden read -rsp "Pass: " var; echo
Timeout read -t 10 -rp "…" var
Single key read -n 1 -rp "…" key
Pre-filled default read -e -i default -rp "…" var
Split fields IFS=, read -r a b c <<< "$str"
Into array read -ra arr <<< "$str"
File line by line while IFS= read -r l; do …; done < f
From terminal in curl | bash read … < /dev/tty

Related: Bash variables and Bash functions.

  • Bash
  • Scripting
  • Linux