My bash getPass function to ask user for a password
I wrote, for a script that create, format and mount a LUKS partition, a function that show stars in an interactive read substitution command.
getPass() { # getPass <prompt string> [variable name]
local -i strpos=0 show=0
local key subkey string=''
while true; do
if ((show)); then
printf '\r%s %s\e[K\r\e[%dC' \
"$1" "$string" $((${#1}+1+strpos))
else
printf '\r%s %s\e[K\r\e[%dC' \
"$1" "${string//?/*}" $((${#1}+1+strpos))
fi
IFS= read -rsn1 -d '' key
[[ $key == $'\e' ]] &&
while IFS= read -d '' -rsn 1 -t .002 subkey; do
key+=$subkey
done
case $key in
$'\n' ) # Return
break
;;
$'\E[D' ) # Move Cursor Left
strpos=' strpos < 1? 0: strpos-1 '
;;
$'\E[C' ) # Move Cursor Right
strpos=" strpos >= ${#string} ? ${#string} : strpos +1 "
;;
$'\177' ) # Backspace
((strpos)) &&
string=${string:0: strpos -1 }${string: strpos } strpos+=-1
;;
$'\E[3~' ) # Delete
((strpos < ${#string} )) &&
string=${string:0: strpos }${string: strpos + 1 }
;;
$'\E[H' | $'\001' ) # Home or Ctrl+A
strpos=0
;;
$'\E[F' | $'\005' ) # End or Ctrl+E
strpos=${#string}
;;
$'\EOP' | $'\E[19~' | $'\E[24~' ) # F1, F8 or F12
show=1-show
;;
[' '-~] ) # One printable character between ascii 0x20 and 0x7E
string=${string::$strpos}$key${string:$strpos} strpos+=1
;;
esac
done
echo
printf -v "${2:-passWord}" '%s' "$string"
}
Bingings:
- < Move cursor left,
- > Move cursor right,
- Home or Ctrl+a Move cursor at begin of string
- End or Ctrl+e Move cursor at end of string
- Delete Deléte one character under cursor
- Backspace Delete back one character at left of cursor
- F1, F8 or F12 Toggle string visibility (show current string instead of stars)
- Return Validate user input and return string.
Syntax:
getPass <prompt string> [variable name]
Example:
getPass "Enter a new password: " pass1 &&
getPass "Repeat same password: " pass2 &&
if [[ $pass1 == "$pass2" ]]; then
echo "password match!"
else
echo "password don't match"
fi
Enter a new password:
Then hit:
| Hit |
show |
| 1 |
* |
| 4 |
** |
| Ctrl+a |
** |
| > |
** |
| 2 |
*** |
| 3 |
**** |
| F9 |
1234 |
| F9 |
**** |
| Return |
**** |
Enter a new password: ****
Repeat same password:
| Hit |
show |
| 1 |
* |
| 2 |
** |
| 3 |
*** |
| 4 |
**** |
| Return |
**** |
Repeat same password: ****
password match!
With UTF-8 Key symbol 🔑 instead of star *.
For some cosmetic, you could replace 10th line:
"$1" "${string//?/*}" $((${#1}+1+strpos))
by
"$1" "${string//?/🔑}" $((${#1}+1+strpos*2))
Because 🔑 use two columns in terminal, where * use only one.
This sed command will do the job:
sed '\_string//.*strpos *))_{s|/[^/$]*}|/\o360\o237\o224\o221}|;s/pos/pos*2/}' -i getPass
Then:
getPass "Please, enter a new password: " pass1 &&
getPass "Please, repeat same password: " pass2 &&
if [[ $pass1 == "$pass2" ]]; then
echo "password match!"
else
echo "password don't match"
fi
Please, enter a new password: 🔑🔑🔑🔑🔑
Please, repeat same password: 🔑🔑🔑🔑
password don't match
readwith the-rargument when reading passwords. Otherwise backslashes can be lost. None of the answers mention this. The accepted answer to the duplicate question covers this and some more edge cases. E.g. settingIFS=in order to not loose trailing spaces.