Skip to content

Latest commit

 

History

History
1071 lines (804 loc) · 25.8 KB

Tips_And_Tricks.org

File metadata and controls

1071 lines (804 loc) · 25.8 KB

Tips And Tricks

Tips and Tricks

Overview

It is a collection of many tips and tricks for Haskell development.

Configuration files in Haskell Syntax

Configuration files can be written in Haskell since it provides a parser (readMaybe) that can read arbitrary data structures defined as instance of show and read type classes.

Example:

File: serverConf.hs

import Text.Read (readMaybe)
import Text.Printf (printf)
    
type Password = String
type Username = String     

data ServerAuth = AuthEmpty
                | AuthBasic Username Password
                deriving (Eq, Read, Show)

data ServerConf = ServerConf
      {
      serverHost     :: String      
     ,serverPort     :: Int
     ,serverAssets   :: String
     ,serverSecurity :: ServerAuth
      } deriving (Eq, Read, Show)


readConfig1 :: String -> Maybe ServerConf
readConfig1 = readMaybe 

readConfig2 :: String -> Maybe ServerConf
readConfig2 str = readMaybe str 


readConfigFile :: String -> IO (Maybe ServerConf)
readConfigFile configFile = readConfig1 <$> readFile configFile -- Or fmap readConfig1 (readFile configFile)


runServer :: ServerConf -> IO ()
runServer config = do printf "Running server at port %d, listening host %s\n" (serverPort config) (serverHost config) 
                      putStrLn $ "Server Authentication = " ++ show (serverSecurity config)


runServerConfigFile :: String -> IO ()
runServerConfigFile configFile = do
  config <- readConfigFile configFile
  case config of
    Just conf -> runServer conf
    Nothing   -> putStrLn "Error: I can't read the server configuration file"
                               


config1 = ServerConf "0.0.0.0" 8080 "/var/www" AuthEmpty

config2 = ServerConf { serverHost = "0.0.0.0",
                       serverPort = 9090,
                       serverAssets = "/home/arch/www",
                       serverSecurity = AuthBasic "gh0st" "xmkg3p98sfawgav"
                     }
 
                           

Configuration File: server.conf

ServerConf {
  serverHost = "127.0.0.1" 
 ,serverPort = 80 
 ,serverAssets = "/var/www"
 ,serverSecurity = AuthBasic "admin" "xjwxfm2pcfg56hga" 
}

Load the file in the REPL:

> :load /tmp/serverconf.hs 
[1 of 1] Compiling Main             ( /tmp/serverconf.hs, interpreted )
Ok, modules loaded: Main.
> 
> :t ServerConf 
ServerConf :: String -> Int -> String -> ServerAuth -> ServerConf
> 


> config1
ServerConf {serverHost = "0.0.0.0", serverPort = 8080, serverAssets = "/var/www", serverSecurity = AuthEmpty}
>

> config2
ServerConf {serverHost = "0.0.0.0", serverPort = 9090, serverAssets = "/home/arch/www", serverSecurity = AuthBasic "gh0st" "xmkg3p98sfawgav"}
>

> runServer config1
Running server at port 8080, listening host 0.0.0.0
Server Authentication = AuthEmpty
> 
> runServer config2
Running server at port 9090, listening host 0.0.0.0
Server Authentication = AuthBasic "gh0st" "xmkg3p98sfawgav"
> 
>

-- Read configuration file.
-- 
> readConfigFile "/tmp/server.conf" 
Just (ServerConf {serverHost = "127.0.0.1", serverPort = 80, serverAssets = "/var/www", serverSecurity = AuthBasic "admin" "xjwxfm2pcfg56hga"})
> 

> :t readConfigFile "/tmp/server.conf" 
readConfigFile "/tmp/server.conf" :: IO (Maybe ServerConf)
> 

> runServerConfigFile "/tmp/server.conf"
Running server at port 80, listening host 127.0.0.1
Server Authentication = AuthBasic "admin" "xjwxfm2pcfg56hga"
> 
> runServerConfigFile "/etc/fstab"
Error: I can't read the server configuration file
> 

Using undefined to specify function signature before implementation

Undefined can be used to define function signatures before they are implemented.

Example:

File: math1.hs - Before implement the functions.

add :: Int -> Int -> Int
add = undefined

sub :: Int -> Int -> Int
sub x y = x - y        
      
product :: Num a => [a] -> a
product = undefined            

Load in the Repl:

> :load /tmp/math1.hs 
[1 of 1] Compiling Main             ( /tmp/math1.hs, interpreted )
Ok, modules loaded: Main.
> 

> :t add
add :: Int -> Int -> Int
>
> :t Main.add
Main.add :: Int -> Int -> Int
> 


> :t product

<interactive>:1:1:
    Ambiguous occurrence product
    It could refer to either Main.product,
                             defined at /tmp/math1.hs:6:1
                          or Prelude.product,
                             imported from Prelude at /tmp/math1.hs:1:1
                             (and originally defined in Data.Foldable)
> 

> :t Main.product
Main.product :: Num a => [a] -> a
> 

> Main.add 3 5
*** Exception: Prelude.undefined
> 

> Main.product [1, 2, 3, 4, 5]
*** Exception: Prelude.undefined
> 

File: math1.hs after implement the functions.

add :: Int -> Int -> Int
add = \x y -> x + y

sub :: Int -> Int -> Int
sub x y = x - y        
      
product :: Num a => [a] -> a
product xs = foldr (*) 1 xs             

Loading in the repl:

> :t add
add :: Int -> Int -> Int
> 
> add 3 5
8
>

> Main.add 3 5
8
> 


> :t product 

<interactive>:1:1:
    Ambiguous occurrence product
    It could refer to either Main.product,
                             defined at /tmp/math2.hs:9:1
                          or Prelude.product,
                             imported from Prelude at /tmp/math2.hs:1:1
                             (and originally defined in Data.Foldable)
> 
> 
> Main.product [1, 2, 3, 4, 5]
120
> :t Main.product
Main.product :: Num a => [a] -> a
> 

Using pattern matching for simple command line application

The pattern match is useful to develop simple command line applications in a declarative and robust way.

File: calculator.hs

 
import System.Environment  (getArgs)
import System.Exit (exitFailure, exitSuccess)   
import Text.Read (readMaybe)
import Control.Monad 
    
parseNum :: String -> Maybe Double 
parseNum = readMaybe    

parseNumList :: [String] -> Maybe [Double]
parseNumList xs = mapM readMaybe xs                

{- | Perform operation on a single command line argument -}                  
doOperation :: Show b => (a -> b) -> (String -> Maybe a) -> String -> IO ()
doOperation fn fnParser arg =
    case fnParser arg of
      Just x  -> do putStrLn $ "Result = " ++ show (fn x)
                    exitSuccess
                    
      Nothing -> do putStrLn $ "Error: Invalid input '" ++ arg ++ "'"
                    exitFailure 

{- | Perform operation on multiple command line arguments -}
doListOperation :: Show b => ([a] -> b) -> ([String] -> Maybe [a]) -> [String] -> IO () 
doListOperation fn fnParser args =
    case fnParser args of
      Just xs -> do putStrLn $ "Result = " ++ show (fn xs)
                    exitSuccess
                    
      Nothing -> do putStrLn $ "Error: Invalid input '" ++ show args ++ "'"
                    exitFailure
                   
                 
showHelp = putStrLn $ unlines [
             "Calculator App"
            ,"\nOptions\n"
            ," -help           - Show help"
            ," -pi             - Show pi number"
            ," -exp x          - Compute exponential"
            ," -sin x          - Compute sin"
            ," -echo file      - Display file"
            ," -sum 10 20 30   - Compute sum"
            ," -prod 10 20 30  - Compute product"

            ]

       
echoFile file = do
  content <- readFile file
  putStrLn content

parseArgs :: [String] -> IO ()
parseArgs args =
    case args of
      []                -> showHelp >> exitSuccess
      ["-pi"]           -> putStrLn (show 3.1415) >> exitSuccess                     
      ["-help"]         -> showHelp >> exitSuccess
      ["-exp", x]       -> doOperation exp parseNum x
      ["-sin", x]       -> doOperation sin parseNum x
      ["-cos", x]       -> doOperation cos parseNum x                     
      ["-echo", file]   -> echoFile file                      
      "-sum":args       -> doListOperation sum parseNumList args
      "-prod":args      -> doListOperation product parseNumList args
      _                 -> do putStrLn "Error: Invalid command line switches."
                              exitFailure
{-
Or 
   main :: IO () 
   main = do 
      args <- getArgs 
      parseArgs args 


-}                           
main :: IO () 
main = getArgs >>= parseArgs

Command line tests:

Running in as script:

$ stack exec -- runhaskell /tmp/calculator.hs 
Calculator App

Options

 -help           - Show help
 -pi             - Show pi number
 -exp x          - Compute exponential
 -sin x          - Compute sin
 -echo file      - Display file
 -sum 10 20 30   - Compute sum
 -prod 10 20 30  - Compute product


$ stack exec -- runhaskell /tmp/calculator.hs -help
Calculator App

Options

 -help           - Show help
 -pi             - Show pi number
 -exp x          - Compute exponential
 -sin x          - Compute sin
 -echo file      - Display file
 -sum 10 20 30   - Compute sum
 -prod 10 20 30  - Compute product

  
$ stack exec -- runhaskell /tmp/calculator.hs sada
Error: Invalid command line switches.


$ stack exec -- runhaskell /tmp/calculator.hs -exp 1.0
Result = 2.718281828459045

$ stack exec -- runhaskell /tmp/calculator.hs -exp 2.0
Result = 7.38905609893065

$ stack exec -- runhaskell /tmp/calculator.hs -sin 3.1415
Result = 9.265358966049026e-5

$ stack exec -- runhaskell /tmp/calculator.hs -cos x2323
Error: Invalid input 'x2323'

$ stack exec -- runhaskell /tmp/calculator.hs -cos
Error: Invalid command line switches.

$ stack exec -- runhaskell /tmp/calculator.hs -sum 1 2 3 4 5
Result = 15.0

$ stack exec -- runhaskell /tmp/calculator.hs -prod 1 2 3 4 5 
Result = 120.0

$ stack exec -- runhaskell /tmp/calculator.hs -prod 1 2 3 sads 4 5 
Error: Invalid input '["1","2","3","sads","4","5"]'

$ stack exec -- runhaskell /tmp/calculator.hs -echo /etc/issue
Manjaro Linux \r  (\n) (\l)

        
     

Compiling:

$ stack exec -- ghc /tmp/calculator.hs -o /tmp/calculator.bin
[1 of 1] Compiling Main             ( /tmp/calculator.hs, /tmp/calculator.o )
Linking /tmp/calculator.bin ...

$ file /tmp/calculator.bin 
/tmp/calculator.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=b3a0da3f4814ef85dd257b9e81651e2d234bb026, not stripped, with debug_info


$ /tmp/calculator.bin 
Calculator App

Options

 -help           - Show help
 -pi             - Show pi number
 -exp x          - Compute exponential
 -sin x          - Compute sin
 -echo file      - Display file
 -sum 10 20 30   - Compute sum
 -prod 10 20 30  - Compute product

$ /tmp/calculator.bin -pi
3.1415

 
$ /tmp/calculator.bin -exp pi
Error: Invalid input 'pi'
 

$ /tmp/calculator.bin -exp 3.1415
Result = 23.138548663861286

 
$ /tmp/calculator.bin -sum 1 2 3 4 5 6 7
Result = 28.0
 

$ /tmp/calculator.bin -sum 1 nothing 3 4 5 6 asdas
Error: Invalid input '["1","nothing","3","4","5","6","asdas"]'

$ /tmp/calculator.bin -echo /etc/lsb-release
DISTRIB_ID=ManjaroLinux
DISTRIB_RELEASE=17.0
DISTRIB_CODENAME=Gellivara
DISTRIB_DESCRIPTION="Manjaro Linux"

Testing in REPL:

> :t parseArgs 
parseArgs :: [String] -> IO ()
> 
> parseArgs ["-help"]
Calculator App

Options

 -help           - Show help
 -pi             - Show pi number
 -exp x          - Compute exponential
 -sin x          - Compute sin
 -echo file      - Display file
 -sum 10 20 30   - Compute sum
 -prod 10 20 30  - Compute product

*** Exception: ExitSuccess
> 
> parseArgs ["-help", "qasdas", "seds"]
Error: Invalid command line switches.
*** Exception: ExitFailure 1
> 
> parseArgs ["-pi"]
3.1415
*** Exception: ExitSuccess
> 

> 
> parseArgs ["-exp", "1.23"]
Result = 3.4212295362896734
*** Exception: ExitSuccess
> 
> parseArgs ["-sum", "1.23", "2.323", "4.23"]
Result = 7.783
*** Exception: ExitSuccess
> 

> parseArgs ["-echo", "/etc/lsb-release"]
DISTRIB_ID=ManjaroLinux
DISTRIB_RELEASE=17.0
DISTRIB_CODENAME=Gellivara
DISTRIB_DESCRIPTION="Manjaro Linux"

> 

Debug Computations using trace

The module Debug.Trace provides functions to allow debug computations.

FunctionSignatureDescription
trace::String -> a -> aPrint the first argument (string) and returns the second.
traceShow::Show a => a -> b -> bPrint the first value and returns the second.
traceIO::String -> IO ()Like trace but works in a IO Monad.

Example:

import Debug.Trace

let x = 10 + 20 in trace ("the value of (10 + 20) is = " ++ show x) x
> the value of (10 + 20) is = 30
30
it


:{
factorial :: Integer -> Integer
factorial 0 = 1
factorial n = trace ("n = " ++ show n) n * factorial (n -1)
:}


> factorial 5
n = 5
n = 4
n = 3
n = 2
n = 1
120
it :: Integer
>

> factorial 10
n = 10
n = 9
n = 8
n = 7
n = 6
n = 5
n = 4
n = 3
n = 2
n = 1
3628800
it :: Integer
>



:{    
myfoldl :: Show acc => (acc -> x -> acc) -> acc -> [x] -> acc
myfoldl step acc []      =  acc
myfoldl step acc (x:xs)  =  myfoldl step (step acc x) xs                       
:}


> myfoldl (\acc x -> 10 * acc +x) 0 [1, 2, 3, 4, 5]
12345
>     


:{    
myfoldl2 :: Show acc => (acc -> x -> acc) -> acc -> [x] -> acc
myfoldl2 step acc []      =  acc
myfoldl2 step acc (x:xs)  =  let acc' = step acc x
                             in trace ("acc' = " ++ show acc') $ myfoldl2 step acc' xs                       
:}

>  myfoldl2 (\acc x -> 10 * acc +x) 0 [1, 2, 3, 4, 5]
acc' = 1
acc' = 12
acc' = 123
acc' = 1234
acc' = 12345
12345
> 

> let result =  myfoldl2 (\acc x -> 10 * acc +x) 0 [1, 2, 3, 4, 5]

> result
acc' = 1
acc' = 12
acc' = 123
acc' = 1234
acc' = 12345
12345

> result
acc' = 1
acc' = 12
acc' = 123
acc' = 1234
acc' = 12345
12345

> result * 3
acc' = 1
acc' = 12
acc' = 123
acc' = 1234
acc' = 12345
37035
> 

:{
traceLabel :: Show a => String -> a -> a 
traceLabel label value = trace (label ++ show value) value
:}

 
:{    
myfoldl3 :: Show acc => (acc -> x -> acc) -> acc -> [x] -> acc
myfoldl3 step acc []      =  acc
myfoldl3 step acc (x:xs)  =  myfoldl3 step (traceLabel "acc' = " $ step acc x) xs                       
:}

 
>  myfoldl3 (\acc x -> 10 * acc +x) 0 [1, 2, 3, 4, 5]
acc' = 1
acc' = 12
acc' = 123
acc' = 1234
acc' = 12345
12345
> 

Haskell Interactive Shell - GHCI

GHCI configuration file

Example - GHCI without configuration file

$ stack ghci
Configuring GHCi with the following packages: 
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /tmp/ghci7713/ghci-script
Prelude> 
Prelude> import Control.Monad
Prelude Control.Monad> 

Prelude Control.Monad> import System.Directory as D
Prelude Control.Monad D> 

Prelude Control.Monad D> D.getDirectoryContents "/etc/" >>= mapM_ putStrLn
systemd
motd
adobe
ld.so.cache
environment
libreoffice
rc_keymaps
sensors3.conf
gshadow
acpi
pkcs11
modules-load.d
cron.deny
shadow-
gufw
security
tmpfiles.d
... ...         

Prelude Control.Monad D> 
Prelude Control.Monad D> :{
Prelude Control.Monad D| getPassword :: String -> IO () -> IO () -> IO ()         
Prelude Control.Monad D| getPassword password success error = do
Prelude Control.Monad D|   passwd <- putStr "Enter the password: " >> getLine 
Prelude Control.Monad D|   if passwd == password
Prelude Control.Monad D|   then success
Prelude Control.Monad D|   else do error
Prelude Control.Monad D|           getPassword password success error
Prelude Control.Monad D| :}
Prelude Control.Monad D> 

Example - GHCI with configuration file

File: ~/.ghci

import Control.Concurrent (forkIO, threadDelay)
import System.Directory (getCurrentDirectory)

-- Set prompts

:set prompt "> "
:set prompt2 "- "

-- Print type after evaluation 
:set +t 

-- Start at tmp directory 
:cd /tmp

-- Add language extensions 
:set -XFlexibleInstances

putStrLn "Wellcome to Haskell"

GHCI Repl:

$ stack ghci
Configuring GHCi with the following packages: 
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Wellcome to Haskell
it :: ()
Loaded GHCi configuration from /home/archbox/.ghci
Loaded GHCi configuration from /tmp/ghci8295/ghci-script
> 

> import Control.Monad
> import System.Directory as D
> 
> D.getDirectoryContents "/etc/" >>= mapM_ putStrLn
systemd
motd
adobe
ld.so.cache
environment
libreoffice
rc_keymaps
sensors3.conf
gshadow

... ... ...

-- Paste this function
{-
getPassword :: String -> IO () -> IO () -> IO ()         
getPassword password success error = do
  passwd <- putStr "Enter the password: " >> getLine 
  if passwd == password
  then success
  else do error
          getPassword password success error
-}

> :{
- getPassword :: String -> IO () -> IO () -> IO ()         
- getPassword password success error = do
-   passwd <- putStr "Enter the password: " >> getLine 
-   if passwd == password
-   then success
-   else do error
-           getPassword password success error
- :}
getPassword :: String -> IO () -> IO () -> IO ()
>


> let fn a b = 10 * a - b
fn :: Num a => a -> a -> a
> fn 10 20
80
it :: Num a => a
> it
80
it :: Num a => a
> 

> :t forkIO
forkIO :: IO () -> IO GHC.Conc.Sync.ThreadId

> :t threadDelay 
threadDelay :: Int -> IO ()
> 
          

> forkIO (forever $ threadDelay 1000000 >> putStrLn "Repeat forever with 1 second delay")
ThreadId 82
it :: GHC.Conc.Sync.ThreadId
> Repeat forever with 1 second delay
Repeat forever with 1 second delay
Repeat forever with 1 second delay
Repeat forever with 1 second delay
Repeat forever with 1 second delay
Repeat forever with 1 second delay
Repeat forever with 1 second delay
Repeat forever with 1 second delay
     .. ... ... ...
   

Extract contents of IO action

It is useful to extract the value wrapped by an IO action to small experiments and interactive commands. Note: It is only possible in the REPL.

Not possible because readFile returns an IO action.

> putStrLn (readFile "/etc/lsb-release")

<interactive>:5:11: error:
     Couldn't match type IO String with [Char]
      Expected type: String
        Actual type: IO String
     In the first argument of putStrLn, namely
        (readFile "/etc/lsb-release")
      In the expression: putStrLn (readFile "/etc/lsb-release")
      In an equation for it’:
          it = putStrLn (readFile "/etc/lsb-release")
>

> :t readFile
readFile :: FilePath -> IO String

> let content = readFile "/etc/lsb-release"

> :t content
content :: IO String
> 
             

> putStrLn content

<interactive>:9:10: error:
     Couldn't match type IO String with [Char]
      Expected type: String
        Actual type: IO String
     In the first argument of putStrLn, namely content
      In the expression: putStrLn content
      In an equation for it’: it = putStrLn content
>

> :t readFile
readFile :: FilePath -> IO String

Solutions:

Extract the IO action content. It is possible due to the Haskell REPL be inside an IO action.

> let contentIOaction = readFile "/etc/lsb-release"

> :t contentIOaction 
contentIOaction :: IO String
> 

> content <- contentIOaction 
>

> :t content
content :: String

> putStrLn content
DISTRIB_ID=ManjaroLinux
DISTRIB_RELEASE=17.0.1
DISTRIB_CODENAME=Gellivara
DISTRIB_DESCRIPTION="Manjaro Linux"

> length content
110
>

> lines content
["DISTRIB_ID=ManjaroLinux","DISTRIB_RELEASE=17.0.1","DISTRIB_CODENAME=Gellivara","DISTRIB_DESCRIPTION=\"Manjaro Linux\""]
> 


{- -- ========================   Or ======================= --}

> content <- readFile "/etc/lsb-release"
> :t content
content :: String


> putStrLn content
DISTRIB_ID=ManjaroLinux
DISTRIB_RELEASE=17.0.1
DISTRIB_CODENAME=Gellivara
DISTRIB_DESCRIPTION="Manjaro Linux"

> length content
110
> 

Pass the IO action content using (>>=) bind or (=<<)

> readFile "/etc/lsb-release" >>= putStrLn
DISTRIB_ID=ManjaroLinux
DISTRIB_RELEASE=17.0.1
DISTRIB_CODENAME=Gellivara
DISTRIB_DESCRIPTION="Manjaro Linux"

 
> putStrLn =<< readFile "/etc/lsb-release"
DISTRIB_ID=ManjaroLinux
DISTRIB_RELEASE=17.0.1
DISTRIB_CODENAME=Gellivara
DISTRIB_DESCRIPTION="Manjaro Linux"

Load interactive commands from file

The Ghci directive :script is load interactive commands that could by typed in Ghci shell from a file.

File: src/script_ghci.hs

content <- readFile "/etc/lsb-release"

let nChars = length content

putStrLn $ "File content = \n" ++ content

putStrLn $ "File size (number of chars) = " ++ show nChars

         
putStrLn "--------------------------------"
         
:{
getPassword :: String -> IO () -> IO () -> IO ()         
getPassword password success error = do
  passwd <- putStr "Enter the password: " >> getLine 
  if passwd == password
  then success
  else do error
          getPassword password success error
:}


:{
showSecreteFile :: String -> IO ()
showSecreteFile file = do
  content <- readFile file
  putStrLn "Secrete File Content"
  putStrLn content
   
:}   
  
         
:{
getPassword2 :: String -> Int -> IO ()
getPassword2 password maxTry = aux (maxTry - 1)
  where
    aux counter = do 
      passwd <- putStr "Enter the password: " >> getLine
      case (counter, passwd) of
        _ | passwd == password
              -> showSecreteFile "/etc/shells"
        
        _ | counter == 0
              -> putStrLn $ "File destroyed after " ++ show maxTry ++ " attempts."                 
        _
              -> do putStrLn $ "Try again. You only have " ++ show counter ++ " attempts."
                    aux (counter - 1)                                      
             
:}
   
let printLine = putStrLn "-----------------------------------------------"

printLine                
putStrLn "Open secrete vault: "
getPassword "guessthepassword" (putStrLn "Vault opened. Ok.") (putStrLn "Error: Wrong password. Try Again")               
printLine
putStrLn "Open secret file: (1)"         
getPassword2 "xyzVjkmArchp972343asx" 3

printLine             
putStrLn "Open secret file: (2)"         
getPassword2 "xyzVjkmArchp972343asx" 3

             

Running:

archbox@ghostpc 18:19 ~/Documents/projects/fp-by-example.tutorial/haskell
$ stack ghci
Configuring GHCi with the following packages: 
GHCi, version 8.0.1: http://www.haskell.org/ghc/  :? for help
Loaded GHCi configuration from /home/archbox/.ghci
Loaded GHCi configuration from /tmp/ghci7633/ghci-script
>

> 
> :script src/script_ghci.hs 
File content = 
DISTRIB_ID=ManjaroLinux
DISTRIB_RELEASE=17.0.1
DISTRIB_CODENAME=Gellivara
DISTRIB_DESCRIPTION="Manjaro Linux"

File size (number of chars) = 110
--------------------------------
-----------------------------------------------
Open secrete vault: 
Enter the password: mxkasdasd
Error: Wrong password. Try Again
Enter the password: 32423asdas
Error: Wrong password. Try Again
Enter the password: guessht^?
Error: Wrong password. Try Again
Enter the password: guessthepassword
Vault opened. Ok.
-----------------------------------------------
Open secret file: (1)
Enter the password: 234asdqa
Try again. You only have 2 attempts.
Enter the password: i dont know
Try again. You only have 1 attempts.
Enter the password: try again
File destroyed after 3 attempts.
-----------------------------------------------
Open secret file: (2)
Enter the password: tell me it
Try again. You only have 2 attempts.
Enter the password: archxfmksjpfUif83432
Try again. You only have 1 attempts.
Enter the password: xyzVjkmArchp972343asx
Secrete File Content
#
# /etc/shells
#

/bin/sh
/bin/bash

# End of file
/bin/zsh
/usr/bin/zsh