Q: I am writing a Linux bash script that opens a port for two hours and then closes it. I use sleep 7200 to make it wait two hours before closing the port. This works fine, but when I walk away it is hard for me to know when the port will be closed. I wanted to make a countdown timer to show how much longer the script will take to run. I found some code on the net but couldn't get any of them to work for me.
A: I am sure there are plenty of ways to make a countdown, but here is what I came up with. Just using some while loops I was able to create a simple countdown timer. In the second section I commented the code to help you understand what each line does.
Simple Bash Countdown Timer
You can change the hour, minute (min), or second (sec) variable to change how long the timer runs.
#!/bin/bash
hour=0
min=0
sec=10
while [ $hour -ge 0 ]; do
while [ $min -ge 0 ]; do
while [ $sec -ge 0 ]; do
echo -ne "$hour:$min:$sec\033[0K\r"
let "sec=sec-1"
sleep 1
done
sec=59
let "min=min-1"
done
min=59
let "hour=hour-1"
done
Here is a video of the timer in action.
Here is the code commented above each line to explain how it works:
#!/bin/bash
# Set variables to your desired time.
hour=2
min=0
sec=0
# begin hour while loop - while hour
#variable is greater than or equal to 0 do minute loop
while [ $hour -ge 0 ]; do
# begin minute loop - while min variable
#is greater than or equal to 0 do second loop
while [ $min -ge 0 ]; do
# begin second loop - while sec variable is greater
# than or equal to 0 print time left
while [ $sec -ge 0 ]; do
# echo time on same line so it overwrites last # line, makes it look like countdown
echo -ne "$hour:$min:$sec\33[0Kr"
# Decrease the sec variable by 1
#each iteration of loop to countdown
let "sec=sec-1"
# wait a second before removing a second
# from the countdown clock
sleep 1
# End second loop
done
# Set second timer back to 59 to start new minute
sec=59
# Decrease min variable by 1 to remove a
# minute off the countdown
let "min=min-1"
# end minute loop
done
# Set minute timer back to 59 to start new hour
min=59
# decrease the hour by 1 to remove hour off the countdown
let "hour=hour-1"
# end hour loop
done
Bash Countdown Timer with Colored Text
If you want to get a little fancier, we can add some color to our bash script. The code below will check to see if the timer reached under a minute, and if so turn the color of the font yellow. When the timer gets to 10 seconds, it turns it red. The tput command is used to hide the cursor to refine it further. I also incorporated the printf statement left in the comments by one of our readers. This adds leading zeros where appropriate.
#!/bin/bash
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[0;33m'
RESET='\033[0m'
hour=0
min=1
sec=11
tput civis
echo -ne "${GREEN}"
while [ $hour -ge 0 ]; do
while [ $min -ge 0 ]; do
while [ $sec -ge 0 ]; do
if [ "$hour" -eq "0" ] && [ "$min" -eq "0" ]; then
echo -ne "${YELLOW}"
fi
if [ "$hour" -eq "0" ] && [ "$min" -eq "0" ] && [ "$sec" -le "10" ]; then
echo -ne "${RED}"
fi
echo -ne "$(printf "%02d" $hour):$(printf "%02d" $min):$(printf "%02d" $sec)\033[0K\r"
let "sec=sec-1"
sleep 1
done
sec=59
let "min=min-1"
done
min=59
let "hour=hour-1"
done
echo -e "${RESET}"
tput cnorm
Bash Countdown Timer Using tput for Screen Painting
Here is another example, this time using tput to color the screen yellow for warning and red for the last ten seconds. Code to follow.
Here is the code for the countdown timer above.
#!/bin/bash
Set Variables
cols=$( tput cols )
rows=$( tput lines )
middle_row=$(( $rows / 2 ))
middle_col=$(( ($cols /2) - 4 ))
hour=0
min=1
sec=12
donso () {
tput sgr0
tput cup $( tput lines ) 0
tput cnorm
}
tput clear
tput bold
tput civis
while [ $hour -ge 0 ]; do
while [ $min -ge 0 ]; do
while [ $sec -ge 0 ]; do
if [ "$hour" -eq "0" ] && [ "$min" -eq "0" ]; then
tput setab 3
tput clear
fi
if [ "$hour" -eq "0" ] && [ "$min" -eq "0" ] && [ "$sec" -le "10" ]; then
tput setab 1
tput clear
fi
tput cup $middle_row $middle_col
echo -ne "$(printf %02d:%02d:%02d $hour $min $sec)\e"
let "sec=sec-1"
sleep 1
done
sec=59
let "min=min-1"
done
min=59
let "hour=hour-1"
done
echo -e "${RESET}"
donso
Leave a Reply Cancel reply
This site uses Akismet to reduce spam. Learn how your comment data is processed.
7 Comments
Join Our Newsletter
Categories
- Bash Scripting (17)
- Basic Commands (50)
- Featured (7)
- Just for Fun (5)
- Linux Quick Tips (98)
- Linux Tutorials (65)
- Miscellaneous (15)
- Network Tools (6)
- Reviews (2)
- Security (32)
- Uncategorized (1)
excellent script. i changed the echo a bit to add a leading zero, when appropriate:
echo -ne "$(printf "%02d" $hour):$(printf "%02d" $min):$(printf "%02d" $sec) 33[0Kr"
Good article, but your printf statements for the leading zeros can be condensed into one: e.g.
printf "%02d:%02d:%02d" $hour $min $sec
Also, use "\e" instead of "\033"
Good stuff, thanks for commenting! Interesting in writing a guest article? 🙂
FWIW, since tput is being used, a "\033[0K" or "\e[K" can be replaced with tput sgr0.
I just realized the code in this article has an off-by-one error - it executes an additional final sleep once the timer has reached 00:00:00. To make this more evident, increase the "sleep 1" to "sleep 3", set hours, mins, secs all to zero (0), and you will see it pause instead of immediately ending.
A simpler approach would be to just count down the total number of seconds. Instead of using
printf
, thedate
command can be used to convert seconds to a time stamp. Here is a short example:#!/usr/bin/env bash
# get time from arg in the format of "hr:mm:ss", "mm:ss", or "ss"
IFS=: read -a args <<< "00:00:00:$1"
read hour min sec << 0 )); do
date -d "@${secs}" -u "+%H:%M:%S"
sleep 1
let secs=( secs - 1 )
done
echo "Time's up!"
Pass a hour:minute:second time like "00:03:12", "3:12", or "12" as a command line argument to indicate how long to count down.
I am going to update the post with your suggestions after I have some time to test. Seriously, any interest in guest writing?
This might serve as an example for how to do a countdown with some nice colors, however I think it is a poor way to solve the issue:
- Using "sleep" and a long running script is a poor way to schedule things
- If the script is interrupted before it end, the port will stay open.
Using an exit trap (1) it would be possible to have the script always close the port on exit. (ex: ctrl-c, normal system shutdown, closing terminal windows). It will however not help in the case the system is halted abruptly (power failure, crash).
I think using systemd timer (2) or the 'at' command would be better way to schedule the command execution. Both provide a way to check the list of upcoming scheduled tasks.
For systemd timer, I believe this is a simple way to schedule a one-off CMD to run:
systemd-run --on-active='2 h' CMD
(1) http://redsymbol.net/articles/bash-exit-traps/
(2) https://wiki.archlinux.org/index.php/Systemd/Timers
Other alternative I found:
a) Somebody developed a Python script to temporarily apply ufw rules. I've not tried it but it look pretty nice. In addition to the python version which is meant to run as CRON, there is a very simple bash version which simply uses 'at'.
https://github.com/joshtronic/tmpufw
b) Apparently netfilter/iptables has support temporary rules using IP sets. IP sets allow to dynamically update iptables rules, for example for managing blacklist or whitelist of IP that change often. I have no experience with it. It might be overkill for just one rule, especially if one don't want to deal with iptable directly and prefers easier front-end such as ufw for example.
http://ipset.netfilter.org/
Sorry for the long rely.... I actually think temporary firewall entry can be really useful.
No need to apologize for a long reply. This is all incredibly useful information and Putorius is all about sharing knowledge and learning together! Thank you for your input.