- Published on
How to do X in Bash
- Authors
- Name
- Yair Mark
- @yairmark
I have written various bash scripts throughout my career. This post will act as a living documents of the different things I do in a bash script as a reminder for myself in future and anyone else that may find it useful. I tried to structure this document in a logical manner, I will play with this structure over time and see what works.
Shebang
As per this question I use the following shebang:
#!/usr/bin/env bash
Sharing Common Variables, Scripts and Locations
Use a .env file for common variables
This file is simply a .sh
file with your environment variables in that you source in one place. For example say we have the following .env
file:
SOME_PROGRAM_VERSION=1.1.0
Then to use it in a script simply source it as below:
# ...
# somewhere at the top of your script
. ./.env
# now you can refer to ${SOME_PROGRAM_VERSION} later in your script
Use common.sh for Common Functions
This is similar to .env
except that it is used to store common functions. For example if you want to be able to easily echo a row of dashes for easier output I might have the following function in my common.sh
file:
#!/usr/bin/env bash
set -e
function horizontal_line() {
echo "----------------------------------------------------------------------------------------------------------------------------------------"
}
And to use it in another script I would do so as follows:
# ...
# somewhere at the top of your script
. ./.env
. ./common.sh
# ...
# in some function where I want to output a horizontal line simply use
horizontal_line
echo "Foo"
horizontal_line
Ensure functions start in a Common Location
At the top of your bash file before any functions simply cd to/path/you/need
.
For example:
cd ~/myapp
function foo() {
# foo does not need to cd to ~/myapp as it is already here
}
function bar() {
# Yup you guessed it! bar is also already in myapp (assuming you run this functions in isolation and not after each other)
}
Working with Files and Folders
Check if a File Exists
if [ -f path/to/file ]; then
echo "File exists"
# do whatever else you want to with the file
else
# do whatever you need to if the file does not exist
fi
See more about this test operator here and a stackoverflow question that goes through other approaches.
Check If a CLI Command is Installed
This is generally of the form:
hash toolname 2>/dev/null || {
# do whatever you want to do if the tool is not present
}
For example to check if docker
is present and output an error if it is not:
hash docker 2>/dev/null || {
echo >&2 "Docker not installed, it is needed to run this script" # by piping to >&2 we are outputting to the error stream
}
Convenience Function - Check if Exists and Suggest Install Step/s
function is_present() {
hash "$1" 2>/dev/null || {
echo "You do not have $1 installed"
echo "Install this using: $2"
exit 1
}
}
# Then use it somewhere else for example:
is_present xsv "brew install xsv"
Only Run a CLI Command if it is Installed
This is similar to the previous one except we want to run a CLI command but only if it exists. To do this we use type
as below:
if type someCommand > /dev/null; then someCommand; fi
Run the Unaliased / Unshadowed Command
Often times a command may be aliased by a more modern version of another command for example find
is aliased to fd: alias find='fd'
. But what if at somepoint you want to use the original find
command (just once off)? Its simple actually you use the command
tool: command find
will call the unshadowed find.
Variables
I always wrap variables in "
in case the value of the variable expands to something with spaces. For example:
"${num_nodes}"
and to subtract from a numeric variable:
"$((num_nodes -1))"
Iterating
for i in $(seq 0 "$((num_nodes -1))"); do echo ------"${i}";cd someDir"${i}";done
Or over multiple lines:
for i in $(seq 0 "$((num_nodes -1))"); do
echo ------"${i}"
cd someDir"${i}"
done
I do not think indentation is required it just helps for readability.
Handing Arguments to a Script
For example if you script needs at least one argument:
if [ $# -lt 1 ]; then
echo 1>&2 "$0: not enough arguments."
echo "Usage: some_command 7"
exit 2
fi
Pass All Arguments to Another Command/Script
someCommand "$@" --some-other-flag
For example if the script was called with bash my-awesome-script.sh someFunction abc 2
then the above example would be evaluated as:
someCommand abc 2 --some-other-flag
See this question for more details.
Useful flags
Break on Errors
#!/usr/bin/env bash
set -e
# the rest of your script
Echo Commands Before Running Them
#!/usr/bin/env bash
set -x
# the rest of your script
Combining Multiple Set Options
#!/usr/bin/env bash
set -ex
# the rest of your script
Functions
whatever_name_you_want() {
foo=${1} # this is the first argument passed for example: `whatever_name_you_want 1` then `foo` is assigned to `1`
}
Calling One Function in a File of Functions
Place the following at the bottom of your bash script:
if declare -f "$1" >/dev/null; then
"$@"
else
echo "'$1' is not a known function name" >&2
exit 1
fi
Then to run just one function pretend it is the one from before whatever_name_you_want 2
and the script it is in is called my_awesome_script.sh
then I would run just this function as follows:
bash my_awesome_script whatever_name_you_want 2
Checking What OS The Script is Running In
function get_running_os_name() {
unameOut="$(uname -s)"
case "${unameOut}" in
Linux*) machine=linux;;
Darwin*) machine=darwin;;
CYGWIN*) echo "Cygwin not supported" && exit 126;;
MINGW*) echo "MinGw not supported" && exit 126;;
*) echo "${unameOut} not supported" && exit 126;;
esac
echo "${machine}"
}
Making a Function Return a Value
function something_that_returns() {
MY_VAR="hello"
# reassign this and do whatever else you need to
# to return the value of MY_VAR we simply:
echo "${MY_VAR}"
}
# later if we want to assign the result of the something_that_returns function we do so as follows:
ANOTHER_VAR=$(something_that_returns)
Files
Waiting for Files to be Created
while [ ! -f path/to/someFile.txt ]
do
echo "Waiting for someFile.txt to be created"
sleep 1
done
Replacing All Text in a File In-Place
# -i means in-place, leave this out to not replace the text in the file and instead output the replaced file contents
sed -i "s/texttoreplace/newtext/g" path/to/somefile
See this answer for more details.
String Manipulation
Get the Last N Characters of a String
foo="1234567890"
# to get the last 3 characters of this string i.e. 890
echo -n $foo | tail -c 3
# to assign this to another variable
bar=$(echo -n $foo | tail -c 3)
See this answer for more details.
Responses and Formatting
Echo an Error
This can easily be done as follows:
echo "Some error to output"
exit 1
A convenience function for this would be:
function echoerr() {
echo "$@" 1>&2
exit 1
}
# And using it...
echoerr "Some error to output"
Pretty Print Numbers
The below function will output numbers in thousands format:
function pretty_print_num() {
printf "%'d\n" "$1"
}
pretty_print_num 12345
# 12,345
Calculate and Output Percentage
Care has to be taken when multiplying as it will affect precision.
function percentage() {
echo "$((100 * $1 / $2))%"
}
percentage 27 100
# 27%