2010-10-31 10 views
23

Il seguente programma Haskell richiede all'utente una password nel terminale e continua, se è entrato quello corretto:richiedere una password in Haskell applicazione a riga di comando

main = do 
    putStrLn "Password:" 
    password <- getLine 

    case hash password `member` database of 
     False -> putStrLn "Unauthorized use!" 
     True -> do 
       ... 

Purtroppo, la password apparirà sul schermo come l'utente lo digita, che voglio evitare.

Come posso leggere una sequenza di caratteri che l'utente digita senza visualizzare sullo schermo? Qual è l'equivalente di getLine per questo scopo?

Sono su MacOS X, ma mi piacerebbe che funzionasse anche su Windows e Linux.

+1

Usa [haskeline] (http://hackage.haskell.org/packages/archive/haskeline/0.6.3.1/doc/html/System -console-Haskeline.html # v: getPassword). –

+1

@ TomMd: il tuo commento è una risposta appropriata alla domanda. Perché non renderlo una risposta reale in modo che possa essere votato, commentato, accettato, eccetera? –

risposta

32

provare il prossimo:

module Main 
where 

import System.IO 
import Control.Exception 

main :: IO() 
main = getPassword >>= putStrLn . ("Entered: " ++) 

getPassword :: IO String 
getPassword = do 
    putStr "Password: " 
    hFlush stdout 
    pass <- withEcho False getLine 
    putChar '\n' 
    return pass 

withEcho :: Bool -> IO a -> IO a 
withEcho echo action = do 
    old <- hGetEcho stdin 
    bracket_ (hSetEcho stdin echo) (hSetEcho stdin old) action 
+3

Funziona a meraviglia! Potresti trasformarlo in un piccolo esempio di 'getPassword :: IO String' completo, quindi posso rendere questa la risposta accettata? –

+0

Ho modificato lo snippet – Yuras

+0

Fantastico, grazie! –

9

È possibile disattivare l'eco nel terminale con the System.Posix.Terminal module. Tuttavia, questo richiede il supporto POSIX, quindi potrebbe non funzionare su Windows (non ho controllato).

import System.Posix.Terminal 
import System.Posix.IO (stdInput) 

getPassword :: IO String 
getPassword = do 
    tc <- getTerminalAttributes stdInput 
    setTerminalAttributes stdInput (withoutMode tc EnableEcho) Immediately 
    password <- getLine 
    setTerminalAttributes stdInput tc Immediately 
    return password 

main = do 
    putStrLn "Password:" 
    password <- getPassword 
    putStrLn "Name:" 
    name <- getLine 
    putStrLn $ "Your password is " ++ password ++ " and your name is " ++ name 

noti che lo stdin è la linea tamponata, quindi se si utilizza putStr "Password:" invece di putStrLn, è necessario svuotare il buffer prima, altrimenti la richiesta sarà inibita.

+1

Bello, avere un upvote! Sembra che l'umile 'hSetEcho' di' System.IO' farà il trucco, comunque. :-) –

+0

@Heinrich: Ah. Troppo umile per essere notato: P – kennytm

13

C'è una getPassword in System.Console.Haskeline. Probabilmente è un eccesso per il tuo caso ma qualcuno potrebbe trovarlo utile.

Un esempio:

> runInputT defaultSettings $ do {p <- getPassword (Just '*') "pass:"; outputStrLn $ fromJust p} 
pass:*** 
asd 
+1

Bello! Non sapevo della libreria 'haskeline'. –

+1

Qualche idea sul perché tutte le funzioni di Haskeline restituiscano un 'Maybe String' piuttosto che un' String'? Non c'è documentazione su di esso, e mi sembra che debba sempre restituire 'String', come standard' getLine'. – jameshfisher

+0

Questo perché lo standard 'getLine' è una funzione parziale, che genera un'eccezione quando si riceve un' EOT'. haskeline restituirà invece 'Nothing', consentendo di fare qualcosa di diverso quando si ottiene una linea vuota, rispetto a'^D' – berdario

4

Come ho già commentato in precedenza, ti suggerisco di usare haskeline, che è una libreria pronta pieno. L'ho usato felicemente per LambdaCalculator senza lamentele.

+0

Qualche idea sul perché tutte le funzioni di Haskeline restituiscano un 'Maybe String' piuttosto che un' String'? Non c'è documentazione su di esso, e mi sembra che debba sempre restituire 'String', come standard' getLine'. – jameshfisher

+0

@jameshfisher Non è immediatamente ovvio per me, no. Ci vorrebbe un po 'di scavo nella fonte. –

5

withEcho può essere scritto con un po 'meno rumore:

withEcho :: Bool -> IO a -> IO a 
withEcho echo action = 
    bracket (hGetEcho stdin) 
      (hSetEcho stdin) 
      (const $ hSetEcho stdin echo >> action) 
+1

Penso che questo sia un po 'troppo intelligente per me, devo sembrare piuttosto difficile capire come funziona. Una soluzione migliore potrebbe essere quella di scomporre il comportamento di "eseguire azioni con impostazioni modificate temporaneamente" in qualcosa come "withTemp :: a -> IO a -> (a -> IO b) -> IO c -> IO c',' withTemp tempValue getter setter action = ... 'in modo da poter definire' withEcho echo = withTemp echo (hGetEcho stdin) (hSetEcho stdin) '. –

Problemi correlati