Here’s a quick function that will display a prompt with timeout in a bash script:
_myCountdownFunction () {
echo -e "Hit \"Ctrl+c\" to quit or \"Enter\" to continue... \c"
cnt=5
while (( cnt >= 0 )) ; do
if (( cnt < 9 )) ; then
echo -e "\b\b${cnt}s\c"
elif (( cnt == 9 )) ; then
echo -e "\b\b\b ${cnt}s\c"
elif (( cnt <= 99 )) ; then
echo -e "\b\b\b\b ${cnt}s\c"
elif (( cnt < 999 )) ; then
echo -e "\b\b\b\b${cnt}s\c"
fi
read -t 1 my_reply
(( $? == 0 )) && exit 1
let cnt-=1
done
}
The user will see the following message on his terminal with the seconds counting down in place:
Hit "Ctrl+c" to quit or "Enter" to continue... 5s
At the end of the specified time in ‘cnt’, the script (where the function would be) will continue executing, and if the user hits “Ctrl+c” before that, it will exit (both script and function).
The function supports up to 999s (which should be enough).
Bash Scripting - Best Practices
1 - Readability
1.1 - Indentation
There are 3 commonly used indentation practices for Bash (I prefer the first method, however all 3 are “accepted”):
- 2 spaces
- 4 spaces
- tabs (usually 8 spaces)
All examples will be shown using the first indentation method, however for reference here’s a comparison between all 3.
Example:
## 2 spaces
if ...
command
else ...
command
fi
## 4 spaces
if ...
command
else ...
command
fi
## Tabs
if ...
command
else ...
command
fi
Indentation for if
conditional statements
if [ test ] ; then
command
elif [ test ] ; then
command
else
command
fi
Indentation for for
statements
for a b c in $A ; do
command
done
Indentation for while
and until
loops
while [ true ] ; do
command
done
Indentation for case
statements
case $VAR in
true) command ;;
false)
command1
command2
;;
*)
if [ test ] ; then
command
fi
;;
esac
Indentation for functions
_funct_do_var() {
commands
}
You should always comment your code. This will make it easier for others to understand, as well as for yourself should you need to change the script a few months (or years) later.
Make sure that the comments make sense. Do not try to save on typing as additional comments can save you (or someone else) a lot of time in the future.
Example: Commonly used comments
startTMATE() {
## Starts tmate handling
# Launch tmate in a detached state
$TMATE_BIN -S $TMT_SOCKET new-session -d
# Blocks until the SSH connection is established
$TMATE_BIN -S $TMT_SOCKET wait tmate-ready
# Prints the SSH connection string
$TMATE_BIN -S $TMT_SOCKET display -p '#{tmate_ssh}' > $LOG
# Prints the read-only SSH connection string
$TMATE_BIN -S $TMT_SOCKET display -p '#{tmate_ssh_ro}' >> $LOG
}
Example: In-line comments
## Sets up package manager aliases based on distro
# Settings for Ubuntu
if [[ "$DISTRO" =~ (Ubuntu|LinuxMint) ]] ; then
alias aptdate='sudo apt-get update' # Updates package list
alias aptgrade='sudo apt-get upgrade' # Updates all packages
alias apts='apt-cache search' # Search for package
alias aptrm='sudo apt-get remove' # Removes package
aptinst() { # Install package
if [ -d "${HOME}/bin/var/log" ] ; then
LOG=${HOME}/bin/var/log/$(hostname)-package-install.log
fi
echo -e "$(date)\t-\tInstalling packages: $*" >> "$LOG"
sudo apt-get install "$*"
}
fi
Here Document
You can use a here document (EOF
) to “trick” Bash in creating block comments.
Example:

You can read a bit on my other post here.
Bash Built-in Command
You can also use Bash’s bultin :
, which does nothing.
Here’s the definition from Bash’s man page:
: [arguments]
No effect; the command does nothing beyond expanding arguments and
performing any specified redirections. A zero exit code is returned.
Example:
: '
your comments here
'
1.3 - Lines
Breaking Line of Code
When breaking long lines of code with \
, indent the new lines.
Example:
[ -f "/etc/cups/cupsd.conf" ] && echo \
"The CUPS configuration file exists"
Using Empty Lines
You can use empty lines to keep your code clean, even inside code blocks.
Example:
# Downloads this weeks photos
if [ "$THIS_WEEKS_PHOTOS_URL" ] ; then
cd ${UNSPLASH_DIR}
for PHOTO_URL in $THIS_WEEKS_PHOTOS_URL ; do
# Checks if jpg is in the URL
if [[ $(echo $PHOTO_URL | grep -qi jpg ; echo $?) -eq 0 ]] ; then
PHOTO_FILE_NAME=$(echo $PHOTO_URL | awk -F/ '{print $4}')
# else, let's add it
else
PHOTO_FILE_NAME=$(echo $PHOTO_URL | awk -F/ '{print $4}').jpg
fi
# Get the photo and save as file name
wget --progress=bar $PHOTO_URL -O $PHOTO_FILE_NAME
done
fi
1.4 - Misc
- Avoid the use of back-ticks ‘`’ for command substitution. Use
$(...)
for better readability
2 - Headers and Sections
Add descriptive headers to the script outlining it’s name, what it does, usage and possibly version.
#!/bin/bash
################################################################################
################################################################################
# Name: tmate.sh
# Usage:
# Description: Runs tmate and outputs remote string to file
# Created: 2014-10-31
# Last Modified:
# Copyright 2014, Victor Mendonca - http://wazem.org
# License: Released under the terms of the GNU GPL license
################################################################################
################################################################################
2.2 - Sections
Divide your script into sections.
Put all global script variables in one section
#-------------------------------------------------------------------------------
# Sets variables
#-------------------------------------------------------------------------------
Put all functions in one section
#-------------------------------------------------------------------------------
# Functions
#-------------------------------------------------------------------------------
Put all major work in one section (where functions get called)
#-------------------------------------------------------------------------------
# Starts script
#-------------------------------------------------------------------------------
3 - Variables
3.1 - Naming
- Give meaningful names to variables
- When using uppercase, make sure the variable is not already being used
- Avoid starting variables with
_
3.2 - Calling
Double quote variables to prevent globbing or word splitting.
4 - Functions
Try to define your functions at the beginning of the script (see sections).
4.1 - Naming
- Should start with lower case
- It’s good to start with
_
- Upper and lower case are also good
Example:
_lowerIt () {
BASH_MAIN_VERSION=$(echo $BASH_VERSION | awk -F. '{print $1}')
if [[ $BASH_MAIN_VERSION -lt 4 ]] ; then
echo "Not supported with your version of bash"
return
fi
if [ "$1" ] ; then
echo ${1,,}
fi
}
4.2 - Local Variables
Keep your function variables local if they are not being used outside of the function.
Example: This script will output “$2400” only once
_myFunction() {
local extract
extract='$2400'
echo "$extract"
}
_myFunction
echo "$extract"
5- Code Smart
New and Deprecated Features
Get to know your version of Bash and any features it may have.
Example: Old and new arithmetic expansion
# New way for arithmetic expansion
$ echo $((4*25/2))
50
# Old way for arithmetic expansion, which will be deprecated
$ echo $[4*25/2]
50
New features
Bash 4 packs new features (like parameter expansion and output redirects). Get familiar with them as they can save you a lot of time.
Examples: Parameter expansion
$ title="bash best practices"
## Case substitution
$ echo ${title^^}
BASH BEST PRACTICES
## String substitution
$ echo ${title/best/worst}
bash worst practices
Examples: stdout and stderr output redirect
# New
ls 123 &> /dev/null
# Old
ls 123 > /dev/null 2>&1
6 - Software
You can also use software to help with your scripts.
6.1 - Syntax Highlighting
Most of today’s editors include syntax highlighting, even VIM. Syntax highlighting will help you catch mistakes like a unclosed bracket or quote.
6.2 - Lint
Some editors, like Sublime Text include plugins for lint software. They can be of great help for you coding.
There are also sites that can help checking your script (similar to lint), or a small set of commands.
References