We can use the command stty -echo
with a bash shell script to prevent characters to be echoed to the console, e.g., for password input.
Is there a way to print a *
for each input character instead, and to delete them when pressing backspace?
We can use the command stty -echo
with a bash shell script to prevent characters to be echoed to the console, e.g., for password input.
Is there a way to print a *
for each input character instead, and to delete them when pressing backspace?
Well, here it is in bash:
#!/bin/bash
prompt="Enter password: "
password=""
# Turn off echo
stty -echo
# Print prompt
printf "$prompt"
while IFS= read -r -s -n1 char; do
if [[ $char == $'\0' ]]; then
break
elif [[ $char == $'\177' ]]; then
if [ ${#password} -gt 0 ]; then
password="${password%?}"
printf "\b \b"
fi
else
password+="$char"
printf "*"
fi
done
# Turn echo back on
stty echo
printf "\n"
# The password is now stored in the $password variable
echo "Password entered: $password"
This code will prompt the user for a password, display * for each character typed, and handle backspaces correctly. The entered password is stored in the password variable.
Some programming languages have that functionality already built-in.
stty -echo
if you're also using -s
with read
, is there?
It maybe a bit overkill, but if you have GPG's pinentry
programs installed, you can use those to present a nice interface to the user for password prompts, including asterisks for masking characters (enabled by default), password strength meters, password confirmation, etc. However, it uses a command-response system for communication over a pipe, instead of being simply invokable using options directly. So you send commands over pinentry
's input to customise the prompt, actually prompt for the password, etc., and then parse the output (which will include status messages and such) to get the password.
Something like:
get_password() {
# Get locale information https://unix.stackexchange.com/a/757655/70524
eval "$(locale)"
# Use `$TTY` if available (zsh), `$(tty)` otherwise (bash)
TTY=${TTY:-"$(tty)"}
{
echo SETPROMPT "Password:" # Set the prompt
# Add more such customization commands here
echo SETDESC "Enter password for $USER" # Set the description
# Then finally ask for the password itself.
echo GETPIN
} |
# pinentry assumes the frontend will be invoked in the background,
# and needs to be explicitly told what environment it should use
pinentry-curses --ttytype "$TERM" --ttyname "$TTY" --lc-ctype "$LC_CTYPE" --lc-messages "$LC_MESSAGES" |
while IFS= read -r line; do
case $line in
OK*) continue;;
D*) pass=${line#D }; printf "%s\n" "$pass";; # `D`ata line will have the password
*) printf "%s\n" "$line" >&2;; # Dump everything else as possible errors
esac;
done
}
And use it like so:
password=$(get_password)
This will open a full-screen dialog in the terminal:
┌────────────────────────────────────────────────────┐
│ Enter password for muru │
│ │
│ Password: ________________________________________ │
│ │
│ <OK> <Cancel> │
└────────────────────────────────────────────────────┘
(It's a perfect box in my terminal, but might not appear so on the webpage.)
You can also use pinentry-gnome
, pinentry-qt
, pinentry-mac
, etc. for making GUI prompts if the appropriate environment is present.
(D *)
. Replaced that with D*)
, and got this error, bot in konsole
and rxvt
: S ERROR curses.open_tty_for_read 83918929 ERR 83918929 No such file or directory <Pinentry>
Commented
Jul 24 at 1:09
$TTY
is set, unlike in bash. Try with "$(tty)"
instead of "$TTY"
. (Using zsh is also the reason for the case
error, I think).
tty
is sufficient, because both stdin and stdout are redirected. We'll need to use $(tty <&2)
instead, or get the TTY before the pipe. I've updated the post to set $TTY
if unset using tty
.
if you're using bash
, you can just use the -s
option of the read
which suppresses input:
$ help read | grep -- -s
-s do not echo input coming from a terminal
You still need to hack showing the asterisks, but you can do this using the approach described by Dennis Williamson in this SO answer:
#!/bin/bash
unset password
prompt="Enter your password: "
while IFS= read -rsp "$prompt" -n1 char
do
if [[ $char == $'\0' ]]
then
echo ""
break
fi
prompt='*'
password+="$char"
done
printf 'You entered: %s\n' "$password"
However, this doesn't support deleting ay characters as the backspace is also just echoed as one more *
instead of deleting. So Max Haase's answer, which does handle this, is better.