[linux] Hiding user input on terminal in Linux script

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?

This question is related to linux bash scripting

The answer is


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'; 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' 
  else
    # Add typed char to output variable.
    password+=$char
    # Print '*' in its stead.
    printf '*'
  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' in C, 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.


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

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

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

stty -echo
read password
stty echo

simply: hide echo do your stuff show echo


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

for a solution that works without bash or certain features from read you can use stty to disable echo

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

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

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"

Examples related to linux

grep's at sign caught as whitespace How to prevent Google Colab from disconnecting? "E: Unable to locate package python-pip" on Ubuntu 18.04 How to upgrade Python version to 3.7? Install Qt on Ubuntu Get first line of a shell command's output Cannot connect to the Docker daemon at unix:/var/run/docker.sock. Is the docker daemon running? Run bash command on jenkins pipeline How to uninstall an older PHP version from centOS7 How to update-alternatives to Python 3 without breaking apt?

Examples related to bash

Comparing a variable with a string python not working when redirecting from bash script Zipping a file in bash fails How do I prevent Conda from activating the base environment by default? Get first line of a shell command's output Fixing a systemd service 203/EXEC failure (no such file or directory) /bin/sh: apt-get: not found VSCode Change Default Terminal Run bash command on jenkins pipeline How to check if the docker engine and a docker container are running? How to switch Python versions in Terminal?

Examples related to scripting

What does `set -x` do? Creating an array from a text file in Bash Windows batch - concatenate multiple text files into one Raise error in a Bash script How do I assign a null value to a variable in PowerShell? Difference between ${} and $() in Bash Using a batch to copy from network drive to C: or D: drive Check if a string matches a regex in Bash script How to run a script at a certain time on Linux? How to make an "alias" for a long path?