Useful Bash Snippets
I use the command line a lot for different tasks and have over the years collected some gems which I hope may be useful to others as well.
Script header (hashbang)
Using the env
command to run bash avoids hardcoding the bash application path in the script.
#!/usr/bin/env bash
set -o errexit -o pipefail -o nounset # -o xtrace
The errexit
option tells bash to exit the script immediately on any error, and pipefail
makes it exit on first
error in a pipe chain. The nounset
forces all variables to be declared before first use and finally xtrace
will make bash print
the script row before executing it.
Wait for external server to show up
This uses the built-in networking capabilities to detect an open port on a remote host. Once the port is available, the script exits the loop.
until $(bash -c 'cat < /dev/null > /dev/tcp/psql.example.org/5432' &> /dev/null); do sleep 1; done
This can be useful in situations when you need to wait for a service to be available, such as a web- or database server.
Temporary directory
Create a temporary directory which is deleted on script exit.
WORK=$(mktemp -d)
trap 'rm -rf "${WORK}"' EXIT SIGQUIT SIGINT SIGSTOP SIGTERM ERR
Path of current script
The following lines will figure out the name and path of the current bash script.
SCRIPT="$(readlink -f ${BASH_SOURCE[0]})"
SCRIPTPATH="$(dirname ${SCRIPT})"
Check if a directory exists
Use the expression -d
to check if a directory exist in Bash.
Note: Take care that if the target is a symbolic link to a directory, the
expression below will also be true.
WORK=$HOME
if [ -d "$WORK" ]; then
echo "Directory exist: $WORK"
fi
And just to demonstrate the opposite; check if a directory does not exist.
WORK=/foo/bar/baz
if [ ! -d "$WORK" ]; then
echo "Directory does not exist: $WORK"
fi
The quotation characters around the environment variables ensure that the full name of the target is used, even if it contains spaces.
Split a path into directory, name and extension
To extract the file name in a path, use basename
command, and Bash
shell parameter expansion
to extract the extension, name without extension and base name of multiple extensions exist.
EXAMPLE=/home/myuser/doc/file.tar.gz
FULLPATH=$(dirname -- "${EXAMPLE}") # /home/myuser/doc
FULLNAME=$(basename -- "${EXAMPLE}") # file.tar.gz
EXT="${FULLNAME##*.}" # gz
NAME="${FULLNAME%.*}" # file.tar
BASE="${FULLNAME%%.*}" # file
Time calculation
The date
command have an option where you can add or subtract a period of time
to the current date.
$ date -R -d "+10 days"
Sun, 08 Sep 2019 21:24:01 +0700
You can also specify a time to calculate from.
$ date -R -d "Wed, 21 Aug 2019 21:25:31 +0700+8 months"
Tue, 21 Apr 2020 21:25:31 +0700
It is also possible to combine the time periods.
$ date -R -d "Thu, 08 Aug 2019 21:26:35 +0700+8 months+3 days-2 hours"
Sat, 11 Apr 2020 19:26:35 +0700
Another useful function is time zone conversion, the example below converts from Eastern Standard Time (EST) to Central European Time (CET).
$ TZ=CET date -R --date='TZ="EST" 2019-08-14 08:00'
Wed, 14 Aug 2019 15:00:00 +0200
To calculcate the number of days until a specific time, the dates must be converted to seconds then subtracted and divided.
$ echo $((($(date +%s --date "2019-12-31 23:59:59")-$(date +%s))/(3600*24))) days left
Processing command line switches and options
The snippet below demonstrates how to process switches and arguments; an option
that requires an argument (i
), a boolean option (h
) and finally the
remaining argument to the command.
The usage()
statement is a function called to present the command usage syntax.
usage() { echo "Usage: $0 [-i <input>] <destination>" 1>&2; exit 1; }
while getopts ":i:h" o; do
case "${o}" in
i)
INPUT=${OPTARG}
;;
h)
usage
;;
*)
usage
;;
esac
done
shift $((OPTIND-1))
if [ $# -gt 0 ]; then
DESTINATION="$1"
else
echo "Error: Destination must be specified" 1>&2; exit 1
fi
Removing duplicate lines
Removing duplicate lines in a file is easy, while preserving the order.
cat -n unsorted.txt | sort -k2 -k1n | uniq -f1 | sort -nk1,1 | cut -f2-
The above chain of commands prepends the output with a line number, sorts and filters on the text, sorts again on the numeric prefix and finally removes the prefix.
Arrays with file- and directory names
Using readarray
combined with find
you can create an array with names of
directory contents without worrying about spaces or special characters in the name.
readarray -d '' FILES < <(find "$DIR" -maxdepth 1 -print0)
for FILE in "${FILES[@]}"; do
echo $FILE
done