Asking Questions: read
read waits for the user to type a line and keeps it in a variable: -p shows a question first, -s hides what's typed, and ${name:-default} gives an answer that was left empty a default.
Apprentice · Unix · Linux · Updated
After this lesson you can
- Ask the user a question with read -p and keep the answer in a variable
- Hide a typed answer with read -s, and give an empty answer a default with ${name:-default}
Key takeaways
- read name waits for a line of typing and keeps it in the variable name.
- read -p "Question? " name shows the question first, on the same line as the answer.
- read -s hides what's typed, for a password or a surprise. Add an echo after it, because the Enter doesn't show either.
- ${name:-Happy birthday} is name's value, or Happy birthday if it's empty: a default for anyone who presses Enter.
- Use -r on every read, so a backslash in the answer stays a backslash.
Arguments work well when you know what a script needs. Your cousin doesn't: she runs your card maker for the first time, it prints an unsigned card, and she has no idea it wanted a second word. Installers solve this by asking: "Install to which folder?", "Continue? [Y/n]". A script can ask too, wait for an answer and carry on with it. An IT tech's setup script asks for the new user's name; a backup script asks "Delete the old copies? [y/N]" before it does something it can't undo.
The examples use the card shop from Arguments: $1, $# and "$@": cd ~/terminal-practice/cards.
read: wait for an answer
read stops and waits for the user to type a line and press Enter, then keeps that line in a variable. Try it at the prompt:
read name
The prompt doesn't come back: bash is waiting. Type Maya and press Enter, then:
echo "Hello, $name"
Hello, Maya
read reads from the keyboard, the standard input of Three Streams: Output, Errors and Input, so a pipe or a < can answer it too. That's how the exercise's check answers your script's questions without a person.
-p: ask a question first
A blinking cursor with no question is a puzzle. -p (for prompt) shows the question first, and the answer is typed on the same line. Make ask-card.sh:
#!/usr/bin/env bash
# ask-card.sh: asks who the card is for, then makes it.
read -r -p "Who is the card for? " name
echo "Happy birthday, $name!"
End the question with a space, so the answer doesn't touch it. Run it, and type Diego when it asks:
chmod +x ask-card.sh
./ask-card.sh
Who is the card for? Diego
Happy birthday, Diego!
The -r keeps a backslash in the answer as a backslash instead of a special character. It's never wrong to add, and ShellCheck (Writing Scripts in VS Code) asks for it on every read, so every example here uses it. On a Mac, these lines work in a script, which runs in bash 5; typed at zsh's own prompt, read -p means something else.
-s: hide what's typed
Some answers shouldn't appear on the screen: a password, or a surprise when the birthday girl is sitting next to you. -s (for silent) hides the typing. Make secret-card.sh:
#!/usr/bin/env bash
# secret-card.sh: a card with a surprise inside, typed where no one can see it.
read -r -p "Who is the card for? " name
read -r -s -p "The surprise (hidden as you type): " surprise
echo
echo "Dear $name, $surprise" > "card-for-$name.txt"
echo "Saved card-for-$name.txt. No peeking!"
Run it, answer Maya, then type You are getting a puppy! for the surprise:
chmod +x secret-card.sh
./secret-card.sh
cat card-for-Maya.txt
Who is the card for? Maya
The surprise (hidden as you type):
Saved card-for-Maya.txt. No peeking!
Dear Maya, You are getting a puppy!
Nothing showed while you typed, not even the Enter at the end, so the lone echo after read -s moves to a new line. sudo hides your password the same way. The answer is hidden, not secret: the script can still print it or save it, as this one does.
A default for an empty answer
Most people send birthday cards, so the card maker shouldn't make everyone type "Happy birthday". Installers show the usual answer in square brackets, and pressing Enter picks it. In bash, ${greeting:-Happy birthday} (read "greeting, or Happy birthday if it's empty") gives you the variable's value, or the default when there isn't one. Make greeting.sh:
#!/usr/bin/env bash
# greeting.sh: asks for a greeting, and offers one if you press Enter.
read -r -p "Who is the card for? " name
read -r -p "Greeting [Happy birthday]: " greeting
greeting=${greeting:-Happy birthday}
echo "$greeting, $name!"
Run it twice. The first time, answer Diego and press Enter at the greeting; the second, answer Aunt Rosa and Thank you:
chmod +x greeting.sh
./greeting.sh
./greeting.sh
Who is the card for? Diego
Greeting [Happy birthday]:
Happy birthday, Diego!
Who is the card for? Aunt Rosa
Greeting [Happy birthday]: Thank you
Thank you, Aunt Rosa!
The same works for arguments: ${2:-Sam} is the second argument, or Sam when there isn't one. Arguments suit a script that other scripts or a schedule run, with no one there to answer; questions suit a person at the keyboard. Many tools take an argument when it's given and ask when it isn't.
Check yourself
2 questions to try on the lesson page.
So what
read -r -p turns a script into a conversation: it asks, waits and carries on with the answer, hides what should stay hidden with -s, and offers a sensible default to anyone who presses Enter.