BASH - Timed Input - Show countdown - bash

I have a bash script that asks the user for their details.
I'm setting a limit to how long we wait for the input. I've found this and it appears to what I want.
timelimit=5
echo -e " You have $timelimit seconds\n Enter your name quickly: \c"
name=""
read -t $timelimit name
#read -t $timelimit name <&1
# for bash versions bellow 3.x
if [ ! -z "$name" ]
then
echo -e "\n Your name is $name"
else
echo -e "\n TIME OUT\n You failed to enter your name"
fi
It shows "You have 5 seconds..." any way to update the output so it shows 4,3,2,1 etc as it counts down ?
Thanks

I have tried most of these answers and none of them worked perfectly for me.
Been playing with this for a local developer deployment script.
This solves a few of the issues noted, like including printed output, etc.
Also wrapped as a function for portability. I'm keen to see any improvements.
Here is my solution:
#!/bin/bash
# set -euo pipefail
READTIMEOUT=5
function read_yn {
MESSAGE=$1
TIMEOUTREPLY=$2
NORMALREPLY="Y"
if [ -z "${TIMEOUTREPLY}" ]; then
TIMEOUTREPLY="Y"
fi
TIMEOUTREPLY_UC=$( echo $TIMEOUTREPLY | awk '{print toupper($0)}' )
TIMEOUTREPLY_LC=$( echo $TIMEOUTREPLY | awk '{print tolower($0)}' )
if [ "${TIMEOUTREPLY_UC}" == "Y" ]; then
NORMALREPLY="N"
fi
NORMALREPLY_UC=$( echo $NORMALREPLY | awk '{print toupper($0)}' )
NORMALREPLY_LC=$( echo $NORMALREPLY | awk '{print tolower($0)}' )
for (( i=$READTIMEOUT; i>=0; i--)); do
printf "\r${MESSAGE} [${NORMALREPLY_UC}${NORMALREPLY_LC}/${TIMEOUTREPLY_UC}${TIMEOUTREPLY_LC}] ('${TIMEOUTREPLY_UC}' in ${i}s) "
read -s -n 1 -t 1 waitreadyn
if [ $? -eq 0 ]
then
break
fi
done
yn=""
if [ -z $waitreadyn ]; then
echo -e "\nNo input entered: Defaulting to '${TIMEOUTREPLY_UC}'"
yn="${TIMEOUTREPLY_UC}"
else
echo -e "\n${waitreadyn}"
yn="${waitreadyn}"
fi
}
read_yn "TESTING" "y"
GIST: https://gist.github.com/djravine/7a66478c37974940e8c39764d59d35fa
LIVE DEMO: https://repl.it/#DJRavine/read-input-with-visible-countdownsh

This should work and shouldn't overwrite input, bit more long winded than the other solutions.
#!/bin/bash
abend()
{
stty sane
exit
#Resets stty and then exits script
}
DoAction(){
stty -echo
#Turn off echo
tput sc
#Save cursor position
echo -ne "\033[0K\r"
# Remove previous line
tput cuu1
#Go to previous line
tput el
#clear to end of line
echo "You have $(($time-$count)) seconds"
#Echo timer
echo -n "$Keys"
#Echo currently typed text
stty echo
#turn echo on
tput rc
#return cursor
}
main()
{
trap abend SIGINT # Trap ctrl-c to return terminal to normal
stty -icanon time 0 min 0 -echo
#turn of echo and set read time to nothing
keypress=''
time=5
echo "You have $time seconds"
while Keys=$Keys$keypress; do
sleep 0.05
read keypress && break
((clock = clock + 1 ))
if [[ clock -eq 20 ]];then
((count++))
clock=0
DoAction $Keys
fi
[[ $count -eq $time ]] && echo "you have run out of time" && abend
done
stty sane
echo Your username was $Keys
echo "Thanks for using this script."
exit 0
}
main

This seems to work:
$ cat test.sh
total=5 # total wait time in seconds
count=0 # counter
while [ ${count} -lt ${total} ] ; do
tlimit=$(( $total - $count ))
echo -e "\rYou have ${tlimit} seconds to enter your name: \c"
read -t 1 name
test ! -z "$name" && { break ; }
count=$((count+1))
done
if [ ! -z "$name" ] ; then
echo -e "\nyour name is $name"
else
echo -e "\ntime out"
fi

#!/bin/bash
timelimit=6
name=""
for (( i = 1 ; i <= $timelimit; i++ )); do
echo -ne "\rYou have $(expr $timelimit - $i) seconds. Enter your name quickly: \c"
[ ! -z "$name" ] && { break ; }
read -t 1 name
done
if [ -z "$name" ]; then
echo -e "\n TIME OUT\n You failed to enter your name"
else
echo -e "\n Your name is $name"
fi
this should work

This works fine and fast for me:
#!/bin/bash
#Sets starttimestamp
starttime=$(date +%s)
#Sets timeout
timeout=5
#sets successflag default to false
success=false
#Save Cursorposition
echo -n -e "\033[s"
#While time not up
while [ $(($starttime+$timeout)) -gt $(date +%s) ] ; do
#Return to saved Cursorpositon
echo -n -e "\033[u"
#Display time left
echo "$(((starttime+timeout)-$(date +%s))) seconds left"
#Ask for 1 char then go on in loop make it look like an ongoing input by adding the user variable to the prompt
if read -p foo="Username: $user" -n 1 -t 1 c ; then
#If user hits return in time c will be empty then break out of loop and set success true
if [[ $c == "" ]] ; then
success=true
break
fi
# Append latest character to user variable
user=${user}${c}
unset c
fi
done
if $success ; then
echo "Yiha!"
else
echo "Too late!"
fi

Related

Can anyone suggest why my code for postgresql walarchiveclean up is not working...I give two arguments as parameters but still it does not go forward

This is the code for wal archive clean up in postgresql. I am passing archive path and age before when wal needs to be cleaned up. Its a wrapper scrip but somehow its not working. Whenever I pass arguments it throws first message for passing correct parameters even though I am giving correct one.
#!/bin/bash
if [ "$#" -ne 2 ]; then
echo -e "This script helps in cleaning up archived log files in postgres. Give the command with parameters in order\t
sh walarchivecleanup.sh -p archivepath -a age (days) "
echo "Usage : walarchivecleanup.sh -p archivepath -a age"
echo -e "\t -p <value> -- Path to the archived WAL logs (e.g. /pg_data/pg_xlog/archive)"
echo -e "\t -a <value> -- Age of archived logs to keep (days), anything older will be deleted"
exit 1
else
echo -e "Do Nothing"
fi
archivepath=$1
age=$2
##########################################################
while getopts "p:a" opt;
do
case ${opt} in
p) archivepath=${OPTARG};;
a) age=${OPTARG};;
\? )
echo "Usage: sh walarchivecleanup.sh -p archivepath -a age (days) "
;;
esac
done
###############################################################
if [[ -z $archivepath ]]; then echo "Error: Missing archivepath"; exit 1; fi
if [[ -z $age ]]; then echo "Error: Age (-a) must be given"; exit 1; fi
if ! [[ -d $archivepath ]]; then
echo "Error: archivepath not found"; exit 1
else
cmd_path=$archivepath
fi
if [[ -n $archivecleanup ]]; then
if ! [[ -x $archivecleanup ]]; then
echo "Error: Command $archivecleanup not found or no permission to execute"; exit 1;
else
cmd_command="$archivecleanup"
fi
else
if ! `which pg_archivecleanup 1>/dev/null`; then echo "Error: Command pg_archivecleanup not found"; exit 1; fi
cmd_command="pg_archivecleanup"
fi
if [[ -n $age ]]; then
cmd_file="$(find ${archivepath}/ -type f -mtime +${age} -printf "%C# %f\n" |sort -n | tail -n 1 | awk '{print $NF}')"
else
cmd_file="$archivefile"
fi
execute="$cmd_command $cmd_path $cmd_file"
`$execute`
exit $?
echo "Unknown Error - Should never reach this part"
exit 1

Daemon script - sh: unknown operand; arithmetic syntax error

I might be blind, but I can't find the errors my script got, maybe you guys got better eyes than me :). I use a busybox compiled linux on a embedded system, Kernel 4.18.0. I found the base script here: Gist-Template
the following error is at "start":
./daemon: line 195: arithmetic syntax error
when I try "stop" these messages appear, but i dont see a unknown operand at line 0:
sh: 0: unknown operand
* Stopping Monitoring
my script:
#!/bin/sh
daemonName="Monitoring"
pidDir="."
pidFile="$pidDir/$daemonName.pid"
pidFile="$daemonName.pid"
logDir="."
# To use a dated log file.
# logFile="$logDir/$daemonName-"`date +"%Y-%m-%d"`".log"
# To use a regular log file.
logFile="$logDir/$daemonName.log"
# Log maxsize in KB
logMaxSize=1024 # 1mb
runInterval=300 # In seconds
doCommands() {
# This is where you put all the commands for the daemon.
echo "Running commands."
}
############################################################
# Below are the command functions
############################################################
hw_temp() {
cpu_temp=$(sensors|grep CPU|awk '{print $3}'|awk '{print ($0-int($0)<0.499)?int($0):int($0)+1}')
env_temp=$(sensors|grep ENV|awk '{print $3}'|awk '{print ($0-int($0)<0.499)?int($0):int($0)+1}')
pcb_temp=$(sensors|grep PCB|awk '{print $3}'|awk '{print ($0-int($0)<0.499)?int($0):int($0)+1}')
echo "$cpu_temp $env_temp $pcb_temp" >> /opt/monitoring/bla
}
############################################################
# Below is the skeleton functionality of the daemon.
############################################################
myPid=`echo $$`
setupDaemon() {
# Make sure that the directories work.
if [ ! -d "$pidDir" ]; then
mkdir "$pidDir"
fi
if [ ! -d "$logDir" ]; then
mkdir "$logDir"
fi
if [ ! -f "$logFile" ]; then
touch "$logFile"
else
# Check to see if we need to rotate the logs.
size=$((`ls -l "$logFile" | cut -d " " -f 8`/1024))
if [[ $size -gt $logMaxSize ]]; then
mv $logFile "$logFile.old"
touch "$logFile"
fi
fi
}
startDaemon() {
# Start the daemon.
setupDaemon # Make sure the directories are there.
if [[ `checkDaemon` = 1 ]]; then
echo " * \033[31;5;148mError\033[39m: $daemonName is already running."
exit 1
fi
echo " * Starting $daemonName with PID: $myPid."
echo "$myPid" > "$pidFile"
log '*** '`date +"%Y-%m-%d"`": Starting up $daemonName."
# Start the loop.
loop
}
stopDaemon() {
# Stop the daemon.
if [[ `checkDaemon` -eq 0 ]]; then
echo " * \033[31;5;148mError\033[39m: $daemonName is not running."
exit 1
fi
echo " * Stopping $daemonName"
log '*** '`date +"%Y-%m-%d"`": $daemonName stopped."
if [[ ! -z `cat $pidFile` ]]; then
kill -9 `cat "$pidFile"` &> /dev/null
fi
}
statusDaemon() {
# Query and return whether the daemon is running.
if [[ `checkDaemon` -eq 1 ]]; then
echo " * $daemonName is running."
else
echo " * $daemonName isn't running."
fi
exit 0
}
restartDaemon() {
# Restart the daemon.
if [[ `checkDaemon` = 0 ]]; then
# Can't restart it if it isn't running.
echo "$daemonName isn't running."
exit 1
fi
stopDaemon
startDaemon
}
checkDaemon() {
# Check to see if the daemon is running.
# This is a different function than statusDaemon
# so that we can use it other functions.
if [ -z "$oldPid" ]; then
return 0
elif [[ `ps aux | grep "$oldPid" | grep "$daemonName" | grep -v grep` > /dev/null ]]; then
if [ -f "$pidFile" ]; then
if [[ `cat "$pidFile"` = "$oldPid" ]]; then
# Daemon is running.
# echo 1
return 1
else
# Daemon isn't running.
return 0
fi
fi
elif [[ `ps aux | grep "$daemonName" | grep -v grep | grep -v "$myPid" | grep -v "0:00.00"` > /dev/null ]]; then
# Daemon is running but without the correct PID. Restart it.
log '*** '`date +"%Y-%m-%d"`": $daemonName running with invalid PID; restarting."
restartDaemon
return 1
else
# Daemon not running.
return 0
fi
return 1
}
loop() {
# This is the loop.
now=`date +%s`
if [ -z $last ]; then
last=`date +%s`
fi
# Do everything you need the daemon to do.
doCommands
# Check to see how long we actually need to sleep for. If we want this to run
# once a minute and it's taken more than a minute, then we should just run it
# anyway.
last=`date +%s`
# Set the sleep interval
if [[ ! $((now-last+runInterval+1)) -lt $((runInterval)) ]]; then
sleep $((now-last+runInterval))
fi
# Startover
loop
}
log() {
# Generic log function.
echo "$1" >> "$logFile"
}
###############################################################
# Parse the command.
###############################################################
if [ -f "$pidFile" ]; then
oldPid=`cat "$pidFile"`
fi
checkDaemon
case "$1" in
start)
startDaemon
;;
stop)
stopDaemon
;;
status)
statusDaemon
;;
restart)
restartDaemon
;;
*)
echo "Error: usage $0 { start | stop | restart | status }"
exit 1
esac
exit 0

getting error when running my bash script

I made this bash script but getting this error when running it: ./admin2.sh: line 78: syntax error near unexpected token else'
./admin2.sh: line 78:else'.
I've edited it many times but i cant seem to find what exactly the error is. this is the script:
#!/bin/bash
if [[ $key == 1029127 ]]
clear
echo -e ""
echo -e -n "${LIGHTRED}[!] ${WHITE}Loading admin menu"
spinner () {
local SP_WIDTH="$3"
local SP_DELAY="$4"
local SP_STRING=${2:-"'|/=\'"}
local SP_COLOR=0
tput civis
while [ -d /proc/$1 ]; do
((RANDOM%2 == 0)) && SP_COLOR=3$((RANDOM%8)) ||
SP_COLOR=9$((RANDOM%8))
printf "\e[1;${SP_COLOR}m\e7 %${SP_WIDTH}s \e8\e[0m" "$SP_STRING"
sleep ${SP_DELAY:-.2}
SP_STRING=${SP_STRING#"${SP_STRING%?}"}${SP_STRING%?}
done
tput cnorm
}
sleep 2.5 &
spinner "$!" '-\\|/' '1.1' '.2'
tput civis
sleep 1
tput cnorm
while true
do
clear
echo -e "${LIGHTCYAN} Welcome"
echo -e ""
echo -e -n "${WHITE}- Current IP:${LIGHTRED} "
w|awk '{if(NR>2){print $3}}' $3
echo -e -n "${WHITE}- Users connected:${LIGHTRED} "
users | wc -w
echo -e "${WHITE}- Admin privileges:${WHITE
[${LIGHTGREEN}Enabled${WHITE}]"
echo -e ""
echo -e "${LIGHTRED} //Announcements//"
echo -e ""
echo -e "${YELLOW}- Type: /help to see commands"
echo -e "\n"
echo -e ""
echo -e ""
echo -e -n "${LIGHTRED}Type: \c"
read answer
else
echo -e ""
echo -e "${LIGHTRED}[!] ${WHITE}Incorrect key, access denied.
fi
You also seem to have forgotten to end the second while loop. You should end it by adding a doneon the line before the else
...
read answer
done
else
echo -e ""
echo -e "${LIGHTRED}[!] ${WHITE}Incorrect key, access denied.
fi
You're missing a then after your if statement on line 2:
if [[ $key == 1029127 ]]
then
...
else
...
fi
Many people prefer to put the then on the same line, as:
if [[ $key == 1029127 ]]; then
...
else
...
fi
Either way I'd encourage you to properly indent your code so that it's easier to read, and be consistent about style choices such as putting then and do on separate lines or not.

Getting command line argument that stores in a variable

I am writing a bash script to finger the first three line of user's info.
ex:
$ ./c.sh bob unknown
Login: bob Name: Bob
Directory: /u1/h7/bob Shell: /bin/tcsh
Office: AA 044, x8361 Home Phone: 000-000-0000
unknown: no such user.
Here is my code so far
#!/bin/bash
if [ $# == 0 ]; then
echo "Usage: ./c.sh Login/Username"
exit
else
i=$#
j=1
while [ "$j" -le "$i" ]; do
finger ${$j} | head -n+3
echo
j=$(($j+1))
done
fi
instead of giving what user types for the command line arguments, ${$j} is giving me the the value of $j, any suggestion and help for how to get the login/username? I've tried $($j), $((j)), ${$j}....
The easy answer: stop using unnecessary indirection:
#!/bin/bash
if (( $# == 0 )); then
echo "Usage: ./c.sh Login/Username"
exit
else
while [[ $1 ]]; do
finger "$1" | head -n+3
echo
shift
done
fi
or…
…
for user; do # equivalent to `for user in "$#"; do`
finger "$user" | head -n+3
…
done
You could write it this way:
i=$#
j=1
while [ $j -le $i ]; do
finger "${#:j++:1}" | head -n+3
echo
done
…but you don't need to work that hard.
#!/bin/bash
if [[ $# -eq 0 ]]; then
echo "Usage: $0 Login/Username"
exit
else
for ARG in "$#"; do
finger "$ARG" | head -n 3
echo # If you want a newline
done
fi
As simple as it can be.

How do I echo stars (*) when reading password with `read`?

What do I need to do for code in Bash, if I want to echo *s in place of password characters (or even just hide the characters completely) when the user types something in using read?
As Mark Rushakoff pointed out, read -s will suppress the echoing of characters typed at the prompt. You can make use of that feature as part of this script to echo asterisks for each character typed:
#!/bin/bash
unset password
prompt="Enter Password:"
while IFS= read -p "$prompt" -r -s -n 1 char
do
if [[ $char == $'\0' ]]
then
break
fi
prompt='*'
password+="$char"
done
echo
echo "Done. Password=$password"
I really liked the answer that Wirone gave, but I didn't like that the backspacing would continue removing characters even back into the "Enter password: " prompt.
I also had some issues where pressing keys too rapidly would cause some of the characters to actually print on the screen... never a good thing when prompting for a password. =)
The following is my modified version of Wirone's answer which addresses these issues:
#!/bin/bash
unset PASSWORD
unset CHARCOUNT
echo -n "Enter password: "
stty -echo
CHARCOUNT=0
while IFS= read -p "$PROMPT" -r -s -n 1 CHAR
do
# Enter - accept password
if [[ $CHAR == $'\0' ]] ; then
break
fi
# Backspace
if [[ $CHAR == $'\177' ]] ; then
if [ $CHARCOUNT -gt 0 ] ; then
CHARCOUNT=$((CHARCOUNT-1))
PROMPT=$'\b \b'
PASSWORD="${PASSWORD%?}"
else
PROMPT=''
fi
else
CHARCOUNT=$((CHARCOUNT+1))
PROMPT='*'
PASSWORD+="$CHAR"
fi
done
stty echo
echo $PASSWORD
read -s should put it in silent mode:
-s Silent mode. If input is coming from a terminal, characters are not echoed.
See the read section in man bash.
I would like to add something to Dennis Williamson's solution:
#!/bin/bash
unset password
echo -n "Enter password: "
while IFS= read -p "$prompt" -r -s -n 1 char
do
# Enter - accept password
if [[ $char == $'\0' ]] ; then
break
fi
# Backspace
if [[ $char == $'\177' ]] ; then
prompt=$'\b \b'
password="${password%?}"
else
prompt='*'
password+="$char"
fi
done
In above example script handles backspace correctly.
Source
I don't know about stars, but stty -echo is your friend:
#!/bin/sh
read -p "Username: " uname
stty -echo
read -p "Password: " passw; echo
stty echo
Source: http://www.peterbe.com/plog/passwords-with-bash
If you don't care about it being interactive, you can simply do
read -s pass
echo "$pass" | sed 's/./*/g'
This will show a * for each character of the entered password after enter is pressed.
stty -echo
read something
stty echo
will stop user input being echoed to the screen for that read. Depending on what you are doing with prompts, you may want to add an extra echo command to generate a newline after the read.
I just made this Bash-specific function based on Dennis Williamson's, Wirone's and Logan VanCuren's answers:
ask() {
local 'args' 'char' 'charcount' 'prompt' 'reply' 'silent'
# Basic arguments parsing
while [[ "${1++}" ]]; do
case "${1}" in
( '--silent' | '-s' )
silent='yes'
;;
( '--' )
args+=( "${#:2}" )
break
;;
( * )
args+=( "${1}" )
;;
esac
shift || break
done
if [[ "${silent}" == 'yes' ]]; then
for prompt in "${args[#]}"; do
charcount='0'
prompt="${prompt}: "
reply=''
while IFS='' read -n '1' -p "${prompt}" -r -s 'char'; do
case "${char}" in
# Handles NULL
( $'\000' )
break
;;
# Handles BACKSPACE and DELETE
( $'\010' | $'\177' )
if (( charcount > 0 )); then
prompt=$'\b \b'
reply="${reply%?}"
(( charcount-- ))
else
prompt=''
fi
;;
( * )
prompt='*'
reply+="${char}"
(( charcount++ ))
;;
esac
done
printf '\n' >&2
printf '%s\n' "${reply}"
done
else
for prompt in "${args[#]}"; do
IFS='' read -p "${prompt}: " -r 'reply'
printf '%s\n' "${reply}"
done
fi
}
It could be used like:
$ ask Username
Username: AzureDiamond
AzureDiamond
$ ask -s Password
Password: *******
hunter2
$ ask First Second Third
First: foo
foo
Second: bar
bar
Third: baz
baz
#nxnev's answer didn't quite work for me, at least on macOS. I simplified it a bit, and now it's flawless:
#!/bin/bash
ask() {
charcount='0'
prompt="${1}: "
reply=''
while IFS='' read -n '1' -p "${prompt}" -r -s 'char'
do
case "${char}" in
# Handles NULL
( $'\000' )
break
;;
# Handles BACKSPACE and DELETE
( $'\010' | $'\177' )
if (( charcount > 0 )); then
prompt=$'\b \b'
reply="${reply%?}"
(( charcount-- ))
else
prompt=''
fi
;;
( * )
prompt='*'
reply+="${char}"
(( charcount++ ))
;;
esac
done
printf '\n' >&2
printf '%s\n' "${reply}"
}
pwd="$(ask Password)"
echo "Your password is $pwd"
#!/bin/bash
echo "------------------------------"
n=7
echo " Enter Password :"
for (( i=1;i<n;i++ ))
do
stty -echo
read -r -s -n 1 char
stty echo
echo -n "*"
pass+="$char"
done
echo " "
echo " Your password : $pass "
echo ""
echo "-------------------------------"

Resources