From: Mark Carroll
Subject: Invisible read-line
Date: 
Message-ID: <Bbn*XQV7n@news.chiark.greenend.org.uk>
Is there any easy way to do a read-line from stdin connected to a Unix
terminal such that what's being typed doesn't appear on the screen,
e.g. for passwords, etc.?

-- Mark

From: Kent M Pitman
Subject: Re: Invisible read-line
Date: 
Message-ID: <sfwbtc43r18.fsf@world.std.com>
Mark Carroll <·····@chiark.greenend.org.uk> writes:

> Is there any easy way to do a read-line from stdin connected to a Unix
> terminal such that what's being typed doesn't appear on the screen,
> e.g. for passwords, etc.?

I'm pretty sure most implementations can do this for you in some ways,
but there is no portable way to access this functionality.  Whether it's
in the form of READ-LINE, though, or some lower level character thing
is open to question.

Note that part of the problem is that in some systems the natural way
to do this is to pop up a separate dialog to read a password.  And in
other systems, you're reading text from an editor buffer and it is not
meaningful to suppress echo on that stream since the characters are being
inserted into the buffer.

There are two separate notions here:  the idea of "a stream which is not
doing echo" (which is orthogonal to the question of whether the stream from
which you are reading is saving the characters persistently) and the
notion of "requesting a secret thing".

For example, I might give you a READ-LINE-NO-ECHO command but if you
call it on an editor buffer stream, that would just mean it wouldn't
echo the characters back.  But ALREADY, for example in LispWorks,
characters being read from the editor buffer are not being echoed back
(since they were already being echoed by the editor when you typed
them in, often long before the system called the reader function).

And, by contrast, I might give you a thing that prompted for a password,
but that won't help you write something that reads from stdin without
echoing so that you could write a batch file which perhaps didn't even
have passwords but which you just didn't want to do typeout all over the
place as it read characters.

The problem is more complicated than it looks just at a quick glance,
and this is why CL doesn't offer a solution to it.  It's a thing for
which some well-described facilities could be devised, with some input
from users about what they want and with some work to get all the vendors
to agree. But I think it's not a single, simple, unambiguous concept
that is merely an obvious omission.

(I was going to make an analogy to "religion in the classroom", but given
 the length of the last religious threads on this list, I'm going to pray
 the above explanation stands without the need for extra help.)
From: Steve Gonedes
Subject: Re: Invisible read-line
Date: 
Message-ID: <m24shvizy3.fsf@KludgeUnix.com>
Mark Carroll <·····@chiark.greenend.org.uk> writes:

< Is there any easy way to do a read-line from stdin connected to a Unix
< terminal such that what's being typed doesn't appear on the screen,
< e.g. for passwords, etc.?
<
< -- Mark

Sure. Using Linux and acl I wrote the following which will read a
string from a terminal while printing a `*' for each input character.

You will probably have to replace the first two commands with some
implementation specific code. The following works in acl for linux
though.

;;; Implementation specific code
(defun current-tty ()
  "Return the name of the current tty device as a string"
  (let ((stream (excl:run-shell-command "tty" :output :stream :wait nil)))
    (prog1
        (read-line stream)
      (sys:os-wait))))

(defun shell-command (command)
  "Run a shell command"
  (excl:run-shell-command command))


;;; The rest of the code
(defun echo-off (&optional (tty (current-tty)))
  (shell-command (concatenate 'string "stty raw -echo < " tty)))

(defun echo-on (&optional (tty (current-tty)))
  (shell-command (concatenate 'string "stty cooked echo < " tty)))

(defun reset ()
  ;; In case of an emergency
  (shell-command "reset"))

(defmacro without-echo (&body forms)
  `(unwind-protect
       (let ((*DEBUGGER-HOOK*  ; make sure we switch back to
                               ; cooked-mode if we enter the debugger
                #'(lambda (condition value)
                   (echo-on)
                   value)))
         (echo-off)
         ,@ forms)
     (echo-on)))

(defun read-line-with-stars (&optional
                             (input-stream *standard-input*)
                             (output-stream *standard-output*))
  "Read a string from INPUT-STREAM without echoing the characters"
  (with-output-to-string (buffer)
    (without-echo
     (loop for ch = (read-char input-stream)
         until (or (char= ch #\Return) (char= ch #\Newline))
         do (write-char #\* output-stream)
            (force-output output-stream)
            (write-char ch buffer)))))
From: Fred Gilham
Subject: Re: Invisible read-line
Date: 
Message-ID: <u7aernllh8.fsf@snapdragon.csl.sri.com>
Here's an implementation that turns off echoing for CMUCL.  I've been
using it for a couple years now.  It's strongly non-portable.  I
suspect I stole this code from somewhere but I have no idea where.
Many thanks to whoever wrote it. :-)


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CUT HERE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Function to get input without echoing---for inputting passwords and the like.
;;; Depends on CMUCL unix interface package.
(defun echo-off (fd)
  (when (unix:unix-isatty fd)
    (alien:with-alien ((tios (alien:struct unix:termios)))
      (multiple-value-bind
          (val err)
          (unix:unix-tcgetattr fd (alien:alien-sap tios))
        (when (null val)
          (error "Could not tcgetattr, unix error ~S."
                 (unix:get-unix-error-msg err))))
      (let ((old-c-lflag (alien:slot tios 'unix:c-lflag)))
        (setf (alien:slot tios 'unix:c-lflag)
              (logand (alien:slot tios 'unix:c-lflag)
                      (lognot (logior unix:tty-echo unix:tty-icanon))))
        (multiple-value-bind
            (val err)
            (unix:unix-tcsetattr fd unix:tcsaflush (alien:alien-sap tios))
          (when (null val)
            (error "Could not tcsetattr, unix error ~S."
                   (unix:get-unix-error-msg err))))
        old-c-lflag))))


(defun restore-c-lflag (fd old-c-lflag)
  (when (unix:unix-isatty fd)
    (alien:with-alien ((tios (alien:struct unix:termios)))
      (multiple-value-bind
          (val err)
          (unix:unix-tcgetattr fd (alien:alien-sap tios))
        (when (null val)
          (error "Could not tcgetattr, unix error ~S."
                 (unix:get-unix-error-msg err))))
      (setf (alien:slot tios 'unix:c-lflag) old-c-lflag)
      (multiple-value-bind
          (val err)
          (unix:unix-tcsetattr fd unix:tcsaflush (alien:alien-sap tios))
        (when (null val)
          (error "Could not tcsetattr, unix error ~S."
                 (unix:get-unix-error-msg err))))))
  (values))


(defun get-input-no-echo (stream)
  (let ((input "")
        (fd (system:fd-stream-fd (if (common-lisp::two-way-stream-p stream)
                                     (two-way-stream-output-stream stream)
                                   stream))))
    (let ((old-c-lflag (echo-off fd)))
      (unwind-protect
          (setf input (read-line stream))
        (restore-c-lflag fd old-c-lflag)))
    input))


(defun prompt-and-read-reply (prompt)
  (format t prompt)
  (force-output)
  (read-line))

(defun prompt-and-read-reply-no-echo (prompt)
  (format t prompt)
  (force-output)
  (get-input-no-echo system:*tty*))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CUT HERE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

-- 
Fred Gilham                                     ······@csl.sri.com
"Come to me, all who labor and are heavy laden, and I will give you
rest.  Take my yoke upon you, and learn from me, for I am gentle and
lowly in heart, and you will find rest for your souls.  For my yoke
is easy, and my burden is light."               --Jesus of Nazareth
From: Vassil Nikolov
Subject: Re: Invisible read-line
Date: 
Message-ID: <l03130304b3e202a9f26f@195.138.129.84>
Steve Gonedes wrote:                [1999-08-19 10:53 -0400]

 [...]
  > read a
  > string from a terminal while printing a `*' for each input character.
  [...]
  > (defun echo-off (&optional (tty (current-tty)))
  >   (shell-command (concatenate 'string "stty raw -echo < " tty)))
  > 
  > (defun echo-on (&optional (tty (current-tty)))
  >   (shell-command (concatenate 'string "stty cooked echo < " tty)))
  [...]

I'd make sure that the stream is to a tty (one on which stty works).

I wonder if using curses would fare better.

In any case all solutions along these lines will have a rather limited
applicability.  In a perfect world one might want every subclass of
stream to have an input method that doesn't display input on the
respective output device (or signal an error, perhaps).


Vassil Nikolov
Permanent forwarding e-mail: ········@poboxes.com
For more: http://www.poboxes.com/vnikolov
  Abaci lignei --- programmatici ferrei.





 Sent via Deja.com http://www.deja.com/
 Share what you know. Learn what you don't.
From: Steve Gonedes
Subject: Re: Invisible read-line
Date: 
Message-ID: <m2wvuqzdsc.fsf@KludgeUnix.com>
Vassil Nikolov <···@einet.bg> writes:

< I'd make sure that the stream is to a tty (one on which stty works).

You could also just use `/dev/input' on linux which should work.
 
< I wonder if using curses would fare better.

No. Curses will occasionally call the `exit' function which is not a
great thing for lisp.