Bash Tips

Useful tips to up your Bash game.

If you work with computers, there’s probably been a time where you’ve thought: “Wow, there’s got to be a way I can automate this”.

A computer script is a list of commands designed to be executed by a program. They are used to automate tasks such as data analysis, webpage generation, and system administration. And the ability to understand and create scripts is one of the most sought after skills in the IT world.

In the first four parts of this tutorial, we talked about the basic syntax of bash scripts and how to use conditionals, loops, and functions in bash.

Today, I am going to give you some additional tips for programming in Bash.

Script permissions

You might have already realized that sometimes when you try to execute a script, you see a message like this.

permission denied: ./calculate_age.sh

This is because the current user does not have permission to execute the script. You can correct this behavior by adding executing rights for everyone.

$ chmod +x calculate_age.sh

Or if you just want to grant executing rights for yourself, the owner of the script, you can use this command instead.

$ chmod 700 calculate_age.sh

Environmental variables

Remember when we talked about variables scopes in shell scripts? Variables that are not input parameters are global to the entire script. But if you want other programs to use the variable as well, you will need to export the variable.

export VARIABLE_NAME=VARIABLE_VALUE

Let’s say in one of your scripts, you set the variable VAR. If you don’t export it or source it in another script, the value is destroyed once the script exits.

VAR="hello!"

But if you export VAR in the first script, and run it before running the second script, the second script will be able to read VAR’s value.

export VAR="hello!"

Variables

There are a few more particularities about shell variables. First, you can use curly brackets to mark the beginning and end of a variable. In this command, Bash would not be able to tell where the variable name ends. So it would try to search for the value of the variable named “NAME_file”.

NAME="Vickie"
touch "$NAME_file"

To avoid this issue, you can mark the beginning and end of a variable using curly brackets. This command will create a file named “Vickie_file”.

NAME="Vickie"
touch "${NAME}_file"

Special variables

Some variables in Bash have special meanings. First, “$@” stands for all parameters passed in. And “$#” is the number of input parameters the script was called with. “$0” is the name of the program it was called as. And “$1” – “$9” are the individual input parameters of the program.

If the program was called with more than nine input parameters, you need to use the “shift” command to access the remaining parameters.

shift

“Shift” gets rid of the first remaining input parameter and moves the parameter access index forward. For example, before a shift command, “months” is the first parameter.

$ ./calculate_age.sh($0) months($1) "2000-06-09"($2) "2020-05-07"($3) "2021-06-08"($4)

And after the shift, “2000-06-09” becomes the first parameter.

$ ./calculate_age.sh($0) months "2000-06-09"($1) "2020-05-07"($2) "2021-06-08"($3)

In Unix, commands return “0” on success and a positive integer on failure. The variable “$?” contains the exit value of the last command executed. You can use it to test for execution successes and failures.

#!/bin/sh
chmod 777 script.sh
if [ "$?" -ne "0" ]; then
  echo "Chmod failed. You might not have permissions to do that!"
fi

Another useful special variable is “\(".\) contains the process ID of the current process. This is useful when you need to create temporary files for the script. If you have multiple instances of the same script or program running at the same time, they might each need their own temporary files. In this case, you can create temporary files named “/tmp/script_name_$$” for every one of them.

Beware of spaces

Shell variables are useful, but they are also a big source of bugs. Beware of spaces for all variable substitutions!

NAME="Vickie Li"
touch ${NAME}_file

In this example, if the “NAME” variable contained a string with spaces, the second line would expand into:

touch Vickie Li_file

The touch command would create two files, one named “Vickie” and the other named “Li_file”! To avoid this confusion, we have to add double quotes around our file name:

NAME="Vickie Li"
touch "${NAME}_file"

The second line would be expanded into the following line, and the touch command will create one file named “Vickie Li_file” as intended.

touch "Vickie Li_file"

Special characters

There are also some special characters you should be aware of.

In Unix, the wildcard character, “*”, stands for “all”. For example, this command will print out all the file names in the current directory that has the file extension “.txt”.

$ ls *.txt

Double quotes bind values together and indicate that the values inside quotes are a single value. For example, this command will create two files.

touch Vickie Li_file

While this one will only create one file.

touch "Vickie Li_file"

The dollar “$” sign denotes variables, while backticks “`” indicate code substitution.

NAME="Vickie Li"
echo $NAME # echo the value of the variable "NAME"
echo `whoami` # echo the output of the command whoami

Most special characters, like the wildcard character or the single quote, are not interpreted as special when they are placed in double-quotes. For example, this command will echo the string “abc ‘*’ 123”.

$ echo "abc '*' 123"
abc '*' 123

Another important special character is the backslash “\”. The backslash is the “escape character” in Bash. It tells Bash that a certain character should be interpreted literally, and not as a special character. A backslash is also used before a newline to indicate that the line of code has not ended.

chmod 777 \
script.sh

This is the same as:

chmod 777 script.sh

Special characters like double quotes, dollar sign, backticks, and backslashes are still treated as special within double-quotes. So if you want Bash to treat them literally, you will have to escape them using a backslash.

$ echo "\" is a double quote. \$ is a dollar sign. \` is a backtick. \\ is a backslash."

This command will echo:

" is a double quote. $ is a dollar sign. ` is a backtick. \ is a backslash.

Test conditions

Remember that in our calculate_age script, we used this command to test if a third input parameter exists?

if [ $3 ]

There are more conditions you can test for! Let’s go through some useful test conditions!

The “-eq” and “-ne” flags test for equality and inequality.

if [ $3 -eq "1" ] # returns true if $3 == 1
if [ $3 -ne "1" ] # returns true if $3 != 1

While the “-gt”, “-ge”, “-lt”, and “le” flags test for “greater than”, “greater than or equal to”, “less than”, and “less than or equal to”.

if [ $3 -gt "1" ] # returns true if $3 > 1
if [ $3 -ge "1" ] # returns true if $3 >= 1
if [ $3 -lt "1" ] # returns true if $3 < 1
if [ $3 -le "1" ] # returns true if $3 <= 1

The “-z” and “-n” flags test whether a string is empty or not.

if [ -z "" ] # returns true
if [ -n "abc" ] # returns true

Finally, the “-d”, “-f”, “-r”, “-w”, “-x” flags check for directory and file statuses. You can use them to check the existence and permissions of a file before your shell script operates on them.

if [ -d /bin] # returns true if /bin is a directory that exists
if [ -f /bin/bash ] # returns true if /bin/bash is a file that exists
if [ -r /bin/bash ] # returns true if /bin/bash is a readable file
if [ -w /bin/bash ] # returns true if /bin/bash is a writable file
if [ -x /bin/bash ] # returns true if /bin/bash is an executable file

You can also use “&&” and “||” to combine test expressions.

if [ $3 -gt "1" ] && [ $3 -lt "3" ] # returns true if both are true
if [ $3 -gt "1" ] || [ $3 -lt "0" ] # returns true if at least one is true

Building interactive programs

What if you want to build an interactive program that takes user input during execution? For that, you can use the “read” command. The read command reads user input and stores the input string into a variable.

#!/bin/bash
echo "Hello! Please enter your age."
read AGE
echo "You are $AGE years old."

This program will prompt users to enter their age, and store the user’s age into the variable named AGE.