Throughout this tutorial, we have covered many commands and concepts. Most of the commands we have learned (with the exception of the awk
) command are for strict usage on the command line, but what if you wanted to put some of these in a script to run? You could always write out a long and elaborate command and execute it, but it will not get saved and is difficult to write. Bash scripting solves this problem by allowing you to write common bash commands within a script file and then execute that script file. You may find this useful if you need to do something on a periodic basis. For example, maybe you need to clean out a specific folder on your computer every day and place the contents of it in an archived folder with today's date on it. You can do this through bash scripting, and in this brief introduction, I will teach you how to do just that. First, we need to understand the basics of a scripting file.
The most basic form of a scripting file is shown below. This file is called simple-script.sh
where the .sh
is the file extension for the script (not necessary, but good practice). The permissions on this file are 744
, which means that only we (the owner of the script) can modify or execute it.
#!/bin/bash
echo "I am a useless, basic script"
To run this, we can do so in two ways.
bash simple-script.sh
./simple-script.sh
Notice that the top of the file has something called a "shebang" (#!/bin/bash
), which tells the interpreter which executable to run the script against. In this case, we are telling it to run with the bash shell which is stored in /bin
on our machine. As a note, you generally do not need this shebang as in most cases, your bash shell will execute the scripts by default with bash, but it is good practice and increases portability if you add it.
This is the most basic form of a script, but obviously not all that useful. Throughout this section, we will learn the most important components of a bash script, which includes:
- Variable declarations
- Built-in variables
- Command line arguments
- Reading user input
- for loops
- if-then statements
With these concepts, you should be able to accomplish 95% of your tasks. Sure, there will be times where the above concepts are not enough, but again, this is an introduction to scripting rather than a deep dive. For the rest of this section, you can assume that I have replaced the contents of a file called shell-scripting-basics.sh
every time I run a script unless otherwise noted.
Variable declarations
MY_VARIABLE="some value"
echo $MY_VARIABLE
echo "Variables can also be added within double-quoted strings: $MY_VARIABLE"
echo 'But not single-quoted strings. This will not read the variable: $MY_VARIABLE'
Declaring and using variables is rather simple in bash scripting, so I will not spend a lot of time here.
Built-In Variables
There are several built in variables that you can use in a bash script. They are listed below.
echo $0 # Prints the name of the script - shell-scripting-basics.sh
echo $1 # Prints the first argument given to a script
echo $2 # Prints the second argument given to a script
echo $3 # Prints the third... do I need to continue here???
echo $# # Prints the number of arguments passed to the script
echo $@ # Prints all arguments passed to the script
echo $$ # Prints the process ID
echo $? # Prints the exit code of the previous process run
Command Line Arguments
A shell script can take arguments on the command line. For example, if I ran the following script:
#!/bin/bash
echo "The script $0 evaluates to: " $(($1+$2))
./shell-scripting-basics.sh 3 9
# The script ./shell-scripting-basics.sh evaluates to: 12
This script will evaluate to - "The script ./shell-scripting-basics.sh evaluates to: 12"
As you can see, we can use the built-in variables inside our scripts.
Reading user input
We can also read user input from a script. This is similar to reading arguments, but instead of the user typing their input in before execution time, they type it in during execution.
#!/bin/bash
# Reading the user's input into the user_input variable
read user_input
echo "The user entered: $user_input"
./shell-scripting basics
some input
The user entered: some input
If you want to protect the user input (ex: password entry), just add an -s
at the beginning of the command like so:
read -s user_input
for loops
The syntax for looping in bash is:
#!/bin/bash
for item in $@; do
echo $item
done
In this script, we are looping through all of the user arguments provided to the script. Remember, $@
is a built-in variable containing all of the user arguments. We can also define an array of variables in bash and loop through them.
#!/bin/bash
declare -a my_array=('string 1', 'string 2', 'string 3')
for item in "${my_array[@]}"; do
echo $item
done
The syntax for an array is a bit weird in bash as you can see. We can also use the for loop syntax to loop through a bunch of files. This is a common type of script that you might have to write.
#!/bin/bash
# Navigate to the home directory
cd ~/
# The * refers to all the files and directories in the current directory
# This script is basically the `ls` command
for item in *; do
echo $item
done
if-then statements
I saved if-then statements for last because they can get a bit complicated. The testing syntax that we use for an if-then statement comes from the test
command, and you can find all of the possibilities on the man page by typing man test
. For most commands in bash, the man pages are difficult to digest and are generally unhelpful for finding quick answers, but the man page for the test
command is super straightforward and simple. Therefore, I will not be listing out all of the available testing options and will assume you have read through the man page for test
. Below is a simple use of test
on the command line (outside of a script).
test 2 -eq 2; echo $?
# 0
test 2 -eq 3; echo $?
# 1
If you run these two commands, you will get output of 0 and 1. As we talked about, these numbers are the exit codes of the test commands and are stored in the built-in variable $?
. Each line shown above is actually two commands. First, we test a condition, and second, we print the exit code of the previous command (which was test). If we want to place a test in a script for an if-then statement, we can write it like so:
#!/bin/bash
if [ 2 -eq 2 ]; then
echo "2 does equal 2!"
fi
This will print "2 does equal 2" because the expression evaluates to true (a 0 exit code). We can also test other conditions. For example, we can loop through all the files in our home directory and if they are directories, we will print "$name is a directory" and if not, "$name is a file".
#!/bin/bash
cd ~/
for name in *; do
if [ -d $name ]; then
echo "$name is a directory!"
else
echo "$name is a file!"
fi
done
The -d
flag will test whether a given name is a directory. If it is, it returns true (0 exit code).
Now that you know the basics of bash scripting, we can get to the practical example of checking a specific folder for files and moving them to an archive. In this case, I will be checking a directory for files that have not been modified for 7 days or more and placing them in an archive folder named with today's date. This will incorporate the find
command that we learned earlier!
#!/bin/bash
todays_date=$(date +%Y-%m-%d)
# First check if the archives folder exists
if [ ! -d '/home/zach/archives/' ]; then
mkdir /home/zach/archives/
fi
# Check if today's folder is already created
if [ ! -d "/home/zach/archives/$todays_date" ]; then
mkdir /home/zach/archives/$todays_date
fi
# A bit of a tricky expression here. I looked up on StackOverflow how to pipe the output of the find command
# into a do-while loop because the find exec command gets a permission denied error when running it as a script
find /home/zach/folder-to-clean -type f -mtime -7 |
while read filename
do
mv $filename /home/zach/archives/$todays_date/
done
Functions
A programming language is not a language without functions, so here is the basic syntax for writing and then later calling a function in your bash script.
some_function () {
# If you pass arguments to me, I can print them
if [ $# -eq 0 ]; then
# No arguments were passed to function
echo "I am a function and no arguments were passed to me"
else
# At least 1 argument was passed to me, so i will print all of them
echo "here"
echo $@
fi
# I can return a value that can be retrieved later with the $? built-in variable
return 20
}
# calling our function
some_function
# calling our function with arguments
some_function "argument 1" "argument 2"
# Printing the return value of 20
echo $?
There are endless possibilities to writing bash scripts. Play around a bit and you will be on your way!