在Linux脚本中将用户输入隐藏在终端上


121

我有如下的bash脚本:

#!/bin/bash

echo "Please enter your username";
read username;

echo "Please enter your password";
read password;

我希望当用户在终端上键入密码时,不应显示该密码(或应显示诸如*******之类的内容)。我该如何实现?


2
一般注意事项只是为了防止混淆:此用户名/密码与linux用户名/密码无关-我只是在寻找一种方法来隐藏用户在“读取密码”期间键入的数据。

我添加了一个更新,如果您想通过输入*密码来输出效果的话
SiegeX 2010年

非常感谢。如果有人知道一个问题-这会自动阻止它进入.bash_history吗?


2
我认为不会,bash_history仅捕获您的命令,运行不捕获的命令后会发生什么。
Andreas Wong 2010年

Answers:


270

只需向您的读取调用提供-s,如下所示:

$ read -s PASSWORD
$ echo $PASSWORD

9
我更喜欢这种方式。如果我没有达到我的每日限额,我会投票给你
SiegeX

4
提供上下文:键入输入时不-s显示任何内容。(-s是非POSIX扩展,因此并非所有的shell都支持它,例如ashBusyBox附带的shell;请ssty -echo在此类shell中使用该方法)
mklement0 2014年

3
@electron该页面上的任何内容man实际上都没有建议-s将文本颜色设置为与背景颜色相同。它只是“默默地回响”。 ss64.com/bash/read.html Silent mode. If input is coming from a terminal, characters are not echoed.
Andreas Wong,

2
我在盒子上测试的@electron不仅不会移动光标,而且当我尝试突出显示输入密码的行时,似乎以后也无法粘贴它。如果书中的说法是正确的,那么两者都应该发生。我猜想也许是另一种外壳风格(我正在使用bash 4.1.2(1))。
Andreas Wong

1
@DominykasMostauskis是的,但是标签是用于bash的,因此可以解决。可能存在大量无法使用的外壳变体:)
Andreas Wong

25

更新资料

如果您想通过*为每个键入的字符输出一个来获得喜欢,可以执行以下操作(使用andreas的read -s解决方案):

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

不花哨

echo "Please enter your username";
read username;

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

应该IFS=$'\n'这样,您才能真正完成输入密码。否则很好;非常聪明。
sorpigal

2
无需设置,IFS=$'\n'因为read的默认分隔符为-d $'\n'。在换行符处发生的在nul字符串上中断的if语句使他们能够完成密码。
SiegeX

在使用bash 3.x的FreeBSD上进行测试时,我发现情况并非如此。在Linux上使用3.1.17进行测试可以得出结果。我目前没有手拿BSD的盒子,但是我出于好奇的缘故,明天将尝试确认这一点。
sorpigal

@Sorpigal:有趣,让我知道您发现了什么。我全都
想着

2
我懂了。我的FreeBSD测试基于从您对lesmana帖子的原始错误编辑中复制并粘贴的代码,其中包含一个重要的区别:您已经通过read-d ''。稍后在Linux上重试时,我使用了重新发布的版本。如果我尝试省略-d '',那么它在FreeBSD上的工作原理完全相同!实际上,我对这里没有任何神秘的平台魔术感到很欣慰。
sorpigal 2010年

17

对于没有bash或某些功能的解决方案,read您可以使用stty禁用回显

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

9

这是@SiegeX出色的*打印解决方案的变体,bash 其中增加了对退格的支持;这使用户可以使用backspace密钥(delete在Mac上为key)更正其输入,这通常是密码提示所支持的:

#!/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

注意:


仅POSIX的外壳中(例如,sh在Debian和Ubuntu上,在shdash),请使用stty -echo方法(次优,因为它不打印任何内容),因为read内置不支持-s-n选项。


谢谢,@ Dylan。我刚刚意识到,到目前为止,从输入的密码中删除最后一个字符的代码可以简化-请参见更新。
mklement0 2014年

3

与(lesmana)的答案有些不同(但大都像)

stty -echo
read password
stty echo

简单地:隐藏echo做你的东西show echo


你能解释为什么这比@lesmana的答案更好吗?我看到它更简单,另外一个stty调用的开销更少,堆栈上的变量也更少-只删除回显,而仅恢复回显。有什么可能会破坏其他stty设置的方式吗?Lesmana保留所有设置。
杰夫·普基特

2

这是@SiegeX答案的一种变体,它与传统的Bourne shell(不支持+=分配)一起使用。

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

传统的Bourne外壳(或POSIX兼容外壳)也无法识别[[ ... ]],其read内置功能也没有-s-n选项。因此,您的代码因此无法在中运行dash。(它将在sh实际存在的平台上工作bash,例如在OSX上,在OSX上bash被调用时sh仅修改了一些默认选项,同时仍支持大多数bashisms。)
mklement0 2014年

2
感谢您的反馈,切换到单身,[但如果您read缺少这些选项,我显然无能为力。
Tripleee 2014年

2

我一直喜欢使用Ansi转义字符:

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

8m使文本不可见,并将0m文本重置为“正常”。-e使Ansi转义成为可能。

唯一的警告是您仍然可以复制和粘贴其中的文本,因此,如果您确实需要安全性,则可能不应该使用此文本。

它只是让人们在输入密码时不会查看您的密码。只是之后不要将计算机保持打开状态。:)


注意:

上面是独立于平台的,只要它支持Ansi转义序列即可。

但是,对于另一个Unix解决方案,您可以简单地告诉read不要回显字符...

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

1

获取用户名和密码

使阅读更清晰,但将其放在屏幕上的更好位置

#!/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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.