168

I have bash script like the following:

#!/bin/bash echo "Please enter your username"; read username; echo "Please enter your password"; read password; 

I want that when the user types the password on the terminal, it should not be displayed (or something like *******) should be displayed). How do I achieve this?

5
  • 3
    General note just to prevent confusion: this username/password has got nothing to do with the linux username/password - I am just looking for a way to hide the data that user types during "read password". Commented Nov 30, 2010 at 17:46
  • Thanks much. One question if someone knows - will this automatically prevent it from going into .bash_history? Commented Nov 30, 2010 at 19:09
  • 2
    possible duplicate of How to get a password from a shell script without echoing and How to make bash script ask for a password ? and How do i put stars into read? and others. Commented Nov 30, 2010 at 20:55
  • 3
    I don't think it will, bash_history only captures your command, what happens after running your command it doesn't capture. Commented Dec 1, 2010 at 1:11
  • ss64.com/bash/read.html Commented Sep 21, 2014 at 4:11

9 Answers 9

362

Just supply -s to your read call like so:

$ read -s PASSWORD $ echo $PASSWORD 
Sign up to request clarification or add additional context in comments.

8 Comments

To provide context: -s displays nothing while the input is typed. (-s is a non-POSIX extension, so not all shells support it, such as the ash shell that comes with BusyBox; use the ssty -echo approach in such shells)
Best solution. This - I have noticed - is what almost all password blocks use in Linux and Mac.
@electron Nothing in the man page actually suggests that -s sets the text color to the same as the the background color. It just "echoes silently". ss64.com/bash/read.html Silent mode. If input is coming from a terminal, characters are not echoed.
@electron I tested on my box, not only it doesn't move the cursor, when I try to highlight the line where I typed in my password, I can't seem to paste it later. Both should happen if what the book says is true. I'm guessing maybe a different flavor of shell (I was using bash 4.1.2(1)).
@DominykasMostauskis yeah but the label is for bash, therefore the solution. There are probably tons of shell variants where this don't work :)
|
34

you can use stty to disable echo

this solution works without bash or certain features from read

stty_orig=$(stty -g) stty -echo read password stty $stty_orig 

If you use this in a shell script then also set an exit handler which restores echo:

#! /bin/sh stty_orig=$(stty -g) trap "stty ${stty_orig}" EXIT stty -echo ... 

this is to make sure echo is restored regardless of how the script exits. otherwise the echo will stay off if the script errors out.

to turn echo back on manually type the following command

stty echo 

you will have to type blindly because you do not see what you type.

i suggest to first press ctrl+c to clear anything else you might have typed before.


historical trivia

echo means to echo your typed input back to your screen.

this is from the time when we worked on teletypewriters (tty is short for teletypewriter). a teletypewriter is like a typewriter but connected to another teletypewriter or computer. typically via telephone cable.

the workflow on a teletypewriter is roughly as follows: you type in your command (or message for the other side). the teletypewriter sends the message away. then the teletypewriter will print the response from the other side.

in the end you have a printout of everything you typed and the response of the other side. this is because the teletypewriter is also a typewriter and as such prints the characters as you press them.

when teletypewriters where replaced by screens there was no longer a typewriter which types your input. instead we had to deliberate code an "echo" function which prints your input as you type. this is the echo that we can now turn off.

i do not know whether stty -echo also disabled printing on a teletypewriter.

see here for a teletypewriter in action: https://www.youtube.com/watch?v=2XLZ4Z8LpEE (first part is restoration. action starting at about 12 minutes in)

more teletypewriter restoration: https://www.youtube.com/playlist?list=PL-_93BVApb5-9eQLTCk9xx16RAGEYHH1q

2 Comments

This solution is very helpful for some implementations of the bare "sh" shell, such as Debian's dash shell.
Very cool piece of history!
32

Update

In case you want to get fancy by outputting an * for each character they type, you can do something like this (using andreas' read -s solution):

unset password; while IFS= read -r -s -n1 pass; do if [[ -z $pass ]]; then echo break else echo -n '*' password+=$pass fi done 

Without being fancy

echo "Please enter your username"; read username; echo "Please enter your password"; stty -echo read password; stty echo 

12 Comments

It should be IFS=$'\n' so that you can actually finish typing the password. Otherwise nice; very clever.
No need to set IFS=$'\n' because read's default delimiter is -d $'\n'. The if-statement to break on a nul string, which happens on a newline, is what allows them to finish the password.
In testing on FreeBSD with bash 3.x I find this not to be the case. Testing on Linux with 3.1.17 yields your results. I haven't got a BSD box handy at the moment but I will try to confirm this tomorrow, just for my own curiosity's sake.
@Sorpigal: interesting, let me know what you find out. I'm all about portability
I've got it. My FreeBSD test was based on the code copied and pasted from your original mistaken edit of lesmana's post, which contains one important difference: you had been passing read a -d ''. When I retried it later on Linux I used the reposted version. If I try omitting -d '' of course it works identically on FreeBSD! I am actually quite relieved that there isn't some mysterious platform magic at work here.
|
18

Here's a variation on @SiegeX's excellent *-printing solution for bash with support for backspace added; this allows the user to correct their entry with the backspace key (delete key on a Mac), as is typically supported by password prompts:

#!/usr/bin/env bash password='' while IFS= read -r -s -n1 char; do [[ -z $char ]] && { printf '\n' >/dev/tty; break; } # ENTER pressed; output \n and break. if [[ $char == $'\x7f' ]]; then # backspace was pressed # Remove last char from output variable. [[ -n $password ]] && password=${password%?} # Erase '*' to the left. printf '\b \b' >/dev/tty else # Add typed char to output variable. password+=$char # Print '*' in its stead. printf '*' >/dev/tty fi done 

Note:

  • As for why pressing backspace records character code 0x7f: "In modern systems, the backspace key is often mapped to the delete character (0x7f in ASCII or Unicode)" https://en.wikipedia.org/wiki/Backspace
  • \b \b is needed to give the appearance of deleting the character to the left; just using \b moves the cursor to the left, but leaves the character intact (nondestructive backspace). By printing a space and moving back again, the character appears to have been erased (thanks, The "backspace" escape character '\b': unexpected behavior?).

In a POSIX-only shell (e.g., sh on Debian and Ubuntu, where sh is dash), use the stty -echo approach (which is suboptimal, because it prints nothing), because the read builtin will not support the -s and -n options.

Comments

8

A bit different from (but mostly like) @lesmana's answer

stty -echo read password stty echo 

simply: hide echo do your stuff show echo

2 Comments

Can you explain why this is better than @lesmana's answer? I see it's simpler with less overhead for another stty call, and one less variable on the stack - only taking away echo and only restoring echo. Is there any possible way this could upset other stty settings? Lesmana's preserves all settings.
This answer presumes that echo was on prior to reading the user input (sometimes but not usually a safe assumption). For professional scripts, lesmana's is better.
5

I always like to use Ansi escape characters:

echo -e "Enter your password: \x1B[8m" echo -e "\x1B[0m" 

8m makes text invisible and 0m resets text to "normal." The -e makes Ansi escapes possible.

The only caveat is that you can still copy and paste the text that is there, so you probably shouldn't use this if you really want security.

It just lets people not look at your passwords when you type them in. Just don't leave your computer on afterwards. :)


NOTE:

The above is platform independent as long as it supports Ansi escape sequences.

However, for another Unix solution, you could simply tell read to not echo the characters...

printf "password: " let pass $(read -s) printf "\nhey everyone, the password the user just entered is $pass\n" 

Comments

2

Here is a variation of @SiegeX's answer which works with traditional Bourne shell (which has no support for += assignments).

password='' while IFS= read -r -s -n1 pass; do if [ -z "$pass" ]; then echo break else printf '*' password="$password$pass" fi done 

2 Comments

A traditional Bourne shell (or a POSIX-compliant one) would also not recognize [[ ... ]], nor would its read builtin have the -s and -n options. Your code will therefore not work in dash, for instance. (It will work on platforms where sh is actually bash, such as on OSX, where bash when invoked as sh merely modifies a few default options while still supporting most bashisms.)
Thanks for feedback, switched to single [ but obviously not much I can do if your read lacks these options.
1

Get Username and password

Make it more clear to read but put it on a better position over the screen

#!/bin/bash clear echo echo echo counter=0 unset username prompt=" Enter Username:" while IFS= read -p "$prompt" -r -s -n 1 char do if [[ $char == $'\0' ]]; then break elif [ $char == $'\x08' ] && [ $counter -gt 0 ]; then prompt=$'\b \b' username="${username%?}" counter=$((counter-1)) elif [ $char == $'\x08' ] && [ $counter -lt 1 ]; then prompt='' continue else counter=$((counter+1)) prompt="$char" username+="$char" fi done echo unset password prompt=" Enter Password:" while IFS= read -p "$prompt" -r -s -n 1 char do if [[ $char == $'\0' ]]; then break elif [ $char == $'\x08' ] && [ $counter -gt 0 ]; then prompt=$'\b \b' password="${password%?}" counter=$((counter-1)) elif [ $char == $'\x08' ] && [ $counter -lt 1 ]; then echo prompt=" Enter Password:" continue else counter=$((counter+1)) prompt='*' password+="$char" fi done 

Comments

1

A variation on both @SiegeX and @mklement0's excellent contributions: mask user input; handle backspacing; but only backspace for the length of what the user has input (so we're not wiping out other characters on the same line) and handle control characters, etc... This solution was found here after so much digging!

#!/bin/bash # # Read and echo a password, echoing responsive 'stars' for input characters # Also handles: backspaces, deleted and ^U (kill-line) control-chars # unset PWORD PWORD= echo -n 'password: ' 1>&2 while true; do IFS= read -r -N1 -s char # Note a NULL will return a empty string # Convert users key press to hexadecimal character code code=$(printf '%02x' "'$char") # EOL (empty char) -> 00 case "$code" in ''|0a|0d) break ;; # Exit EOF, Linefeed or Return 08|7f) # backspace or delete if [ -n "$PWORD" ]; then PWORD="$( echo "$PWORD" | sed 's/.$//' )" echo -n $'\b \b' 1>&2 fi ;; 15) # ^U or kill line echo -n "$PWORD" | sed 's/./\cH \cH/g' >&2 PWORD='' ;; [01]?) ;; # Ignore ALL other control characters *) PWORD="$PWORD$char" echo -n '*' 1>&2 ;; esac done echo echo $PWORD 

Comments