46

What do I need to do for code in Bash, if I want to echo *s in place of password characters (or even just hide the characters completely) when the user types something in using read?

Bad Wolf
  • 8,206
  • 4
  • 34
  • 44
Deniz Zoeteman
  • 9,691
  • 26
  • 70
  • 97
  • 5
    If you're within the systemd ecosystem, you can use systemd-ask-password: http://www.freedesktop.org/software/systemd/man/systemd-ask-password.html – CMCDragonkai Jul 20 '14 at 03:44

10 Answers10

73

As Mark Rushakoff pointed out, read -s will suppress the echoing of characters typed at the prompt. You can make use of that feature as part of this script to echo asterisks for each character typed:

#!/bin/bash
unset password
prompt="Enter Password:"
while IFS= read -p "$prompt" -r -s -n 1 char
do
    if [[ $char == $'\0' ]]
    then
        break
    fi
    prompt='*'
    password+="$char"
done
echo
echo "Done. Password=$password"
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 7
    You need to `unset IFS` or add `IFS=` to your while loop otherwise your loop will prematurely break on passwords that contain spaces. Also, you should add the `-r` flag to `read` so that passwords can contain backslashes. – SiegeX Nov 30 '10 at 23:44
  • 1
    `password+="$char"` didn't work on my system. I had to do `password="${password}${char}"` instead. – Brad Mace Sep 18 '12 at 15:19
  • 2
    @BradMace: You must not be using Bash, ksh (93) or zsh or you are using a very old version of Bash (e.g. 2.05b). – Dennis Williamson Sep 18 '12 at 16:47
30

I really liked the answer that Wirone gave, but I didn't like that the backspacing would continue removing characters even back into the "Enter password: " prompt.

I also had some issues where pressing keys too rapidly would cause some of the characters to actually print on the screen... never a good thing when prompting for a password. =)

The following is my modified version of Wirone's answer which addresses these issues:

#!/bin/bash

unset PASSWORD
unset CHARCOUNT

echo -n "Enter password: "

stty -echo

CHARCOUNT=0
while IFS= read -p "$PROMPT" -r -s -n 1 CHAR
do
    # Enter - accept password
    if [[ $CHAR == $'\0' ]] ; then
        break
    fi
    # Backspace
    if [[ $CHAR == $'\177' ]] ; then
        if [ $CHARCOUNT -gt 0 ] ; then
            CHARCOUNT=$((CHARCOUNT-1))
            PROMPT=$'\b \b'
            PASSWORD="${PASSWORD%?}"
        else
            PROMPT=''
        fi
    else
        CHARCOUNT=$((CHARCOUNT+1))
        PROMPT='*'
        PASSWORD+="$CHAR"
    fi
done

stty echo

echo $PASSWORD
Logan VanCuren
  • 409
  • 4
  • 4
  • A more advance version is [ask_password_stars](http://www.ict.griffith.edu.au/anthony/software/#ask_password_stars). It was developed as part of my personal notes on [Cryptogrphy, Password Input](http://www.ict.griffith.edu.au/anthony/info/crypto/passwd_input.txt). – anthony Jan 19 '17 at 07:40
  • I like this answer. I inserted an `echo` before the `break`. – Míng Dec 12 '22 at 09:25
14

read -s should put it in silent mode:

-s     Silent mode.  If input is coming from a terminal, characters are not echoed.

See the read section in man bash.

Mark Rushakoff
  • 249,864
  • 45
  • 407
  • 398
8

I would like to add something to Dennis Williamson's solution:

#!/bin/bash

unset password
echo -n "Enter password: "
while IFS= read -p "$prompt" -r -s -n 1 char
do
    # Enter - accept password
    if [[ $char == $'\0' ]] ; then
        break
    fi
    # Backspace
    if [[ $char == $'\177' ]] ; then
        prompt=$'\b \b'
        password="${password%?}"
    else
        prompt='*'
        password+="$char"
    fi
done

In above example script handles backspace correctly.

Source

Community
  • 1
  • 1
Wirone
  • 3,304
  • 1
  • 29
  • 48
  • It handles DEL "correctly". It doesn't do anything special with backspace (Ctrl-H). – Keith Thompson Jul 06 '14 at 22:44
  • "_Normal_" users rather use standard backspace, not Ctrl+H. Above script does not handle correctly arrow keys too, but hey, it's better to handle backspace and _not_ handle Ctrl+H, or don't handle both? ;) – Wirone Jul 07 '14 at 10:39
  • 2
    Ctrl-H *is* backspace (the Unicode standard calls it `BACKSPACE`). `$'\177'` is DEL (Unicode calls it `DELETE`). Some terminal programs may optionally map the Backspace key to the DEL character, but many "*normal*" users, including myself, map it to the backspace (Ctrl-H) character. A robust solution would examine the user's tty settings. – Keith Thompson Jul 07 '14 at 14:40
  • I didn't mean Unicode. I said only that for standard user backspace is a key on the keyboard, with "backspace" label ;) And this is what is mapped in script. – Wirone Jul 07 '14 at 20:43
  • What I wrote applies to Unicode, ASCII, and Latin-N for various values of N. `$'\177'` is the `DEL` character; the backspace character is `$'\010'` or `'\b'`. What "standard" are you referring to? (When I type Ctrl-V Backspace at a shell prompt, it echoes `^H`; am I violating some standard?) – Keith Thompson Jul 07 '14 at 20:52
  • Dude, I just refer to `keyboard layout`. I didn't see `$'\177'`, `$'\010'` nor `'\b'` on any button. I didn't mean any standards like ASCII. Backspace is considered to delete char before caret, `DEL` - after it, user does not have to know what char/code it sends. EOT. – Wirone Jul 07 '14 at 21:49
  • 2
    Dude, the backspace key on *my* keyboard sends an ASCII BS character (8), *not* an ASCII DEL character (127). My tty settings are such that typing backspace deletes the character before the cursor. It's configurable, and your code assumes a particular configuration that is not universal. There is nothing non-standard or abnormal about my setup, and your code would not work properly with it. – Keith Thompson Jul 07 '14 at 21:54
  • 1
    Instead of hardcoding the character for the backspace, add `backspace=$(tput kbs)` before entering the loop and then compare $char against $backspace. That way it'll work no matter what value the user has set their backspace key to send. – Starfish Jul 18 '14 at 20:19
  • Thanks for `tput`, but to be 100% sure it's working we need to know if user has `tput` in path, which is not obvious (I didn't have, because I'm using Cygwin and had to install `ncurses` to get it). Will try to implement it meanwhile ;) – Wirone Jul 19 '14 at 23:19
  • A more advance version is [ask_password_stars](http://www.ict.griffith.edu.au/anthony/software/#ask_password_stars). It was developed as part of my personal notes on [Cryptogrphy, Password Input](http://www.ict.griffith.edu.au/anthony/info/crypto/passwd_input.txt). – anthony Jan 19 '17 at 07:40
5

I don't know about stars, but stty -echo is your friend:

 #!/bin/sh 
 read -p "Username: " uname 
 stty -echo 
 read -p "Password: " passw; echo 
 stty echo

Source: http://www.peterbe.com/plog/passwords-with-bash

Vinko Vrsalovic
  • 330,807
  • 53
  • 334
  • 373
  • 4
    That hardly seems necessary since `read` has a `-s` option (the question is tagged `bash` so we can assume bash-specific features). Interrupting the `read` command (e.g., by typing control-C) could leave the tty in `-echo` mode. – Keith Thompson Jul 06 '14 at 22:43
4

If you don't care about it being interactive, you can simply do

read -s pass
echo "$pass" | sed 's/./*/g'

This will show a * for each character of the entered password after enter is pressed.

Dino Dini
  • 433
  • 3
  • 6
3
stty -echo
read something
stty echo

will stop user input being echoed to the screen for that read. Depending on what you are doing with prompts, you may want to add an extra echo command to generate a newline after the read.

moonshadow
  • 86,889
  • 7
  • 82
  • 122
3

@nxnev's answer didn't quite work for me, at least on macOS. I simplified it a bit, and now it's flawless:

#!/bin/bash

ask() {
    charcount='0'
    prompt="${1}: "
    reply=''
    while IFS='' read -n '1' -p "${prompt}" -r -s 'char'
    do
        case "${char}" in
            # Handles NULL
            ( $'\000' )
            break
            ;;
            # Handles BACKSPACE and DELETE
            ( $'\010' | $'\177' )
            if (( charcount > 0 )); then
                prompt=$'\b \b'
                reply="${reply%?}"
                (( charcount-- ))
            else
                prompt=''
            fi
            ;;
            ( * )
            prompt='*'
            reply+="${char}"
            (( charcount++ ))
            ;;
        esac
    done
    printf '\n' >&2
    printf '%s\n' "${reply}"
}

pwd="$(ask Password)"

echo "Your password is $pwd"
yspreen
  • 1,759
  • 2
  • 20
  • 44
2

I just made this Bash-specific function based on Dennis Williamson's, Wirone's and Logan VanCuren's answers:

ask() {
  local 'args' 'char' 'charcount' 'prompt' 'reply' 'silent'

  # Basic arguments parsing
  while [[ "${1++}" ]]; do
    case "${1}" in
      ( '--silent' | '-s' )
        silent='yes'
        ;;
      ( '--' )
        args+=( "${@:2}" )
        break
        ;;
      ( * )
        args+=( "${1}" )
        ;;
    esac
    shift || break
  done

  if [[ "${silent}" == 'yes' ]]; then
    for prompt in "${args[@]}"; do
      charcount='0'
      prompt="${prompt}: "
      reply=''
      while IFS='' read -n '1' -p "${prompt}" -r -s 'char'; do
        case "${char}" in
          # Handles NULL
          ( $'\000' )
            break
            ;;
          # Handles BACKSPACE and DELETE
          ( $'\010' | $'\177' )
            if (( charcount > 0 )); then
              prompt=$'\b \b'
              reply="${reply%?}"
              (( charcount-- ))
            else
              prompt=''
            fi
            ;;
          ( * )
            prompt='*'
            reply+="${char}"
            (( charcount++ ))
            ;;
        esac
      done
      printf '\n' >&2
      printf '%s\n' "${reply}"
    done
  else
    for prompt in "${args[@]}"; do
      IFS='' read -p "${prompt}: " -r 'reply'
      printf '%s\n' "${reply}"
    done
  fi
}

It could be used like:

$ ask Username
Username: AzureDiamond
AzureDiamond

$ ask -s Password
Password: *******
hunter2

$ ask First Second Third
First: foo
foo
Second: bar
bar
Third: baz
baz
nxnev
  • 121
  • 1
  • 4
-1
#!/bin/bash
echo "------------------------------"

n=7
echo " Enter Password :"

for (( i=1;i<n;i++ ))
do
    stty -echo
    read -r -s -n 1 char
    stty echo

    echo -n "*"
    pass+="$char"

done

echo " "
echo " Your password : $pass "

echo ""
echo "-------------------------------"
kratenko
  • 7,354
  • 4
  • 36
  • 61
7in
  • 1
  • I'm far from beeing a bash-expert, but I think this code has several issues (like password having a fixed length of 6 and not working with backspaces) – kratenko Jun 23 '14 at 15:09