Bash Shell Scripting Guide
Getting started with Bash shell scripting from scratch!
Introduction
What is Bash?
Bash is a Unix shell and command language written by Brian Fox for the GNU Project as a free software replacement for the Bourne shell.
Bash is the Bourn Again Shell it is the free and enhanced version of the Bourne shell distributed with Linux and GNU operating systems.
What is Shell Scripting?
- A shell script is basically a text file that contains a sequence of commands for a UNIX-based operating system. It is called a shell script because it combines a sequence of commands, that would otherwise have to be typed into the keyboard one at a time, into a single script.
Shell scripting files
Scripting files are basically executable text files with extensions like ' .sh ' (for Linux) ' .cmd ' for windows, in which there is a group of terminal commands are written.
The users should have permission to execute files to run a script in their system.
To run a script in terminal, we have to write the following command in terminal -
$ ./FILE_NAME.sh
Note: The shorthand 755 is often used for scripts as it allows you the owner to write or modify the script and for everyone to execute the script.
Syntax of Bash Scripts
SheBang (#!) -
A sheBang is always used in shell scripting at first line of script. The shebang is a special character sequence in a script file that specifies which program should be called to run the script.
for example in case of bash script, the first line will always be,
#! /bin/bash
- here /bin/bash is the path of bash shell which indicates that Bash shell should be called to run this script.
Variables -
Variables may have their value set in a few different ways. The most common are to set the value directly and for its value to be set as the result of processing by a command or program. You will see examples of both below.
To read the variable we then place its name (preceded by a $ sign) anywhere in the script we would like. Before Bash interprets (or runs) every line of our script it first checks to see if any variable names are present. For every variable it has identified, it replaces the variable name with its value. Then it runs that line of code and begins the process again on the next line.
Command line arguments:
Command line arguments are commonly used and easy to work with so they are a good place to start.
When we run a program on the command line you would be familiar with supplying arguments after it to control its behaviour. For instance we could run the command ls -l /etc. -l and /etc are both command line arguments to the command ls. We can do similar with our bash scripts. To do this we use the variables $1 to represent the first command line argument, $2 to represent the second command line argument and so on. These are automatically set by the system when we run our script so all we need to do is refer to them. Let's look at an example.
1. #!/bin/bash
2. // A simple copy script
3. cp $1 $2
4. // Let's verify the copy worked
5. echo Details for $2
6. ls -lh $2
Let's break it down:
- Line 3 - run the command cp with the first command line argument as the source and the second command line argument as the destination.
- Line 5 - run the command echo to print a message.
- Line 6 - After the copy has completed, run the command ls for the destination just to verify it worked. We have included the options l to show us extra information and h to make the size human readable so we may verify it copied correctly.
There are a few other variables that the system sets for you to use as well.
- $0 - The name of the Bash script.
- $1 - $9 - The first 9 arguments to the Bash script. (As mentioned above.)
- $# - How many arguments were passed to the Bash script.
- $@ - All the arguments supplied to the Bash script.
- $? - The exit status of the most recently run process.
- $$ - The process ID of the current script.
- $USER - The username of the user running the script.
- $HOSTNAME - The hostname of the machine the script is running on.
- $SECONDS - The number of seconds since the script was started.
- $RANDOM - Returns a different random number each time is it referred to.
- $LINENO - Returns the current line number in the Bash script.
Setting own variables:
As well as variables that are preset by the system, we may also set our own variables. This can be useful for keeping track of results of commands and being able to refer to and process them later.
There are a few ways in which variables may be set (such as part of the execution of a command) but the basic form follows this pattern:
1. variable=value
2. //example
3. name=Gaus
Note:
This is one of those areas where formatting is important.
There is no space on either side of the equals ( = ) sign. We also leave off the $ sign from the beginning of the variable name when setting it.
####Quotes :
In the example above we kept things nice and simple. The variables only had to store a single word. When we want variables to store more complex values however, we need to make use of quotes. This is because under normal circumstances Bash uses a space to determine separate items. In this case we can make use of quotes ' ' or " " .
- Single quotes will treat every character literally.
- Double quotes will allow you to do substitution (that is include variables within the setting of the value).
1. name='Gaus Ahmed'
2. echo $name
####Command substitution:
Command substitution allows us to take the output of a command or program (what would normally be printed to the screen) and save it as the value of a variable. To do this we place it within brackets, preceded by a $ sign.
$ myvar=$( ls /etc | wc -l )
$ echo There are $myvar entries in the directory /etc
User Input
If we would like to ask the user for input then we use a command called 'read'. This command takes the input and will save it into a variable.
1. #!/bin/bash
2. read var1
3. echo $var1
More with read:
We are able to alter the behaviour of read with a variety of command line options. (See the man page for read to see all of them.) Two commonly used options however are,
- -p which allows you to specify a prompt.
- -s which makes the input silent.
This can make it easy to ask for a username and password combination like the example below:
1. #!/bin/bash
2. # Ask the user for login details
3. read -p 'Username: ' uservar
4. read -sp 'Password: ' passvar
5. echo
6. echo Thankyou $uservar we now have your login details
- On lines 4 and 5 above we include the prompt within quotes so we can have a space included with it. Otherwise the user input will start straight after the last character of the prompt which isn't ideal from a readability point of view.
Arithmetics -
Depending on what type of work you want your scripts to do you may end up using arithmetic a lot or not much at all. It's a reasonable certainty however that you will need to use arithmetic at some point. Like variables, they are reasonably easy to implement and knowing how to do so is an essential skill in Bash scripting mastery.
There are several ways to go about arithmetic in Bash scripting, but the recommended approach is arithmetic expansion.
In the section on Variables we saw that we could save the output of a command easily to a variable. It turns out that this mechanism is also able to do basic arithmetic for us if we tweak the syntax a little. We do so by using double brackets like so:
$(( expression ))
here is a example to illustrate:
1. #!/bin/bash
2. // Basic arithmetic using double parentheses
3. a=$(( 4 + 5 ))
4. echo $a // 9
5.
6. a=$((3+5))
7. echo $a // 8
8.
9. b=$(( a + 3 ))
10. echo $b // 11
11.
12. b=$(( $a + 4 ))
13. echo $b // 12
14.
15. a=$(( 4 * 5 ))
16. echo $a // 20
If statements -
Bash if statements are very useful. In this section of our Bash Scripting Tutorial you will learn the ways you may use if statements in your Bash scripts to help automate tasks.
Basic if statements
A basic if statement effectively says, if a particular test is true, then perform a given set of actions. If it is not true then don't perform those actions. If follows the format below:
Syntax: if [ some test ] then commands fi
1. #!/bin/bash
2. // Basic if statement
3. if [ $1 -gt 100 ]
4. then
5. echo Hey that\'s a large number.
6. pwd
7. fi
8. date
Let's break it down:
- Line 3 - Let's see if the first command line argument is greater than 100
- Line 5 and 6 - Will only get run if the test on line 3 returns true. You can have as many - commands here as you like.
- Line 5 - The backslash ( \ ) in front of the single quote ( ' ) is needed as the single quote has a special meaning for bash and we don't want that special meaning. The backslash escapes the special meaning to make it a normal plain single quote again.
- Line 7 - fi signals the end of the if statement. All commands after this will be run as normal.
- Line 10 - Because this command is outside the if statement it will be run regardless of the outcome of the if statement.
Nested if statements
Talking of indenting. Here's a perfect example of when it makes life easier for you. You may have as many if statements as necessary inside your script. It is also possible to have an if statement inside of another if statement. For example, we may want to analyse a number given on the command line like so:
1. #!/bin/bash
2. // Nested if statements
3. if [ $1 -gt 100 ]
4. then
5. echo Hey that is a large number.
6. if (( $1 % 2 == 0 ))
7. then
8. echo And is also an even number.
9. fi
10. fi
if-else statements
Sometimes we want to perform a certain set of actions if a statement is true, and another set of actions if it is false. We can accommodate this with the else mechanism.
syntax:
if [ some condition ] then [commands] else [other commands] fi
Now we could easily read from a file if it is supplied as a command line argument, else read from STDIN.
1. #!/bin/bash
2. // else example
3. if [ $# -eq 1 ]
4. then
5. nl $1
6. else
7. nl /dev/stdin
8. fi
Loops -
Bash loops are very useful. In this section of our Bash Scripting Tutorial we'll look at the different loop formats available to us as well as discuss when and why you may want to use each of them.
Loops allow us to take a series of commands and keep re-running them until a particular situation is reached. They are useful for automating repetitive tasks.
There are 3 basic loop structures in Bash scripting which we'll look at below. There are also a few statements which we can use to control the loops operation.
1. While Loop
One of the easiest loops to work with is while loops. They say, while an expression is true, keep executing these lines of code. They have the following format:
syntax:
while [ some condition ]
do
commands
done
In the example below we will print the numbers 1 through to 10:
1. #!/bin/bash
2. // Basic while loop
3. counter=1
4. while [ $counter -le 10 ]
5. do
6. echo $counter
7. ((counter++))
8. done
9. echo All done
Let's break it down:
- Line 3 - We'll initialise the variable counter with it's starting value.
- Line 4 - While the test is true (counter is less than or equal to 10) let's do the following commands.
- Line 6 - We can place any commands here we like. Here echo is being used as it's an easy way to illustrate what is going on.
- Line 7 - Using the double brackets we can increase the value of counter by 1.
- Line 8 - We're at the bottom of the loop so go back to line 5 and perform the test again. If the test is true then execute the commands. If the test is false then continue executing any commands following done.
Note:
A common mistake is what's called an off by one error. In the example above we could have put -lt as opposed to -le (less than as opposed to less than or equal). Had we done this it would have printed up until 9. These mistakes are easy to make but also easy to fix once you've identified it so don't worry too much if you make this error.
2. Until Loop
The until loop is fairly similar to the while loop. The difference is that it will execute the commands within it until the test becomes true.
Syntax:
until [ some condition ]
do
commands
done
1. #!/bin/bash
2. // Basic while loop
3. counter=1
4. until [ $counter -le 10 ]
5. do
6. echo $counter
7. ((counter++))
8. done
9. echo All done
As you can see in the example above, the syntax is almost exactly the same as the while loop (just replace while with until). We can also create a script that does exactly the same as the while example above just by changing the test accordingly.
3. For Loop
The for loop is a little bit different to the previous two loops. What it does is say for each of the items in a given list, perform the given set of commands. It has the following syntax.
Syntax:
for var in list
do
commands
done
The for loop will take each item in the list (in order, one after the other), assign that item as the value of the variable var, execute the commands between do and done then go back to the top, grab the next item in the list and repeat over.
The list is defined as a series of strings, separated by spaces.
Here is a simple example to illustrate:
1. #!/bin/bash
2. // Basic for loop
3. names='Stan Kyle Cartman'
4. for name in $names
5. do
6. echo $name
7. done
8. echo All done
Let's break it down:
- Line 3 - Create a simple list which is a series of names.
- Line 4 - For each of the items in the list $names assign the item to the variable $name and do the following commands.
- Line 6 - echo the name to the screen just to show that the mechanism works. We can have as many commands here as we like.
- Line 8 - echo another command to show that the bash script continued execution as normal after all the items in the list were processed.
Functions -
Functions in Bash Scripting are a great way to reuse code. In this section of our Bash scripting tutorial you'll learn how they work and what you can do with them.
Think of a function as a small script within a script. It's a small chunk of code which you may call multiple times within your script. They are particularly useful if you have certain tasks which need to be performed several times. Instead of writing out the same code over and over you may write it once in a function then call that function every time.
Creating a function is fairly easy. They may be written in two different formats,
syntax:
function_name () {
commands
}
or
function function_name {
commands
}
A few points to note:
- Either of the above methods of specifying a function is valid. Both operate the same and there is no advantage or disadvantage to one over the other. It's really just personal preference.
- In other programming languages it is common to have arguments passed to the function listed inside the brackets (). In Bash they are there only for decoration and you never put anything inside them.
- The function definition ( the actual function itself) must appear in the script before any calls to the function.
Let's look at a simple example:
1. #!/bin/bash
2. // Basic function
3. print_something () {
4. echo Hello I am a function
5. }
6. print_something
7. print_something
Let's break it down:
- Line 3 - We start defining the function by giving it a name.
- Line 4 - Within the curly brackets we may have as many commands as we like.
- Lines 6 and 7 - Once the function has been defined, we may call it as many times as we like and it will execute those commands.
User Interface
This is the final section in the tutorial and I'd like to use it to discuss a very important topic (which is often neglected) the user interface. I've touched on various points regarding the user interface throughout the tutorial but here I'll bring them all together and introduce a few other concepts as well.
When most people think about the user interface they think about the bits the end user sees and how they interact with the tool. For Bash scripts I like to think about the layout and structure of the commands inside the script as well. Bash scripts are often small tools used to automate tedious and repetitive tasks. They are always readable by the end user and often modified to suit changing requirements. Therefore the ease with which the user (often yourself) may modify and extend the script is also very important.
- TPUT command:
tput is a command which allows you to control the cursor on the terminal and the format of content that is printed. It is quite a powerful and complex tool so I'll introduce some of the basics here but leave it up to you to do further research.
Here is an example printing a message in the center of the screen.
#!/bin/bash
# Print message in center of terminal
cols=$( tput cols )
rows=$( tput lines )
message=$@
input_length=${#message}
half_input_length=$(( $input_length / 2 ))
middle_row=$(( $rows / 2 ))
middle_col=$(( ($cols / 2) - $half_input_length ))
tput clear
tput cup $middle_row $middle_col
tput bold
echo $@
tput sgr0
tput cup $( tput lines ) 0
Let's break it down:
- Line 4 - tput cols will tell us how many columns the terminal has.
- Line 5 - tput lines will tell us how many lines (or rows) the terminal has.
- Line 7 - Take all the command line arguments and assign them to a single variable message.
- Line 9 - Find out how many characters are in the string message. We had to assign all the input values to the variable message first as ${#@} would tell us how many command line arguments there were instead of the number of characters combined.
- Line 11 - We need to know what 1/2 the length of the string message is in order to center it.
- Lines 13 and 14 - Calculate where to place the message for it to be centered.
- Line 16 - tput clear will clear the terminal.
- Line 18 - tput cup will place the cursor at the given row and column.
- Line 19 - tput bold will make everything printed to the screen bold.
- Line 20 - Now we have everything set up let's print our message.
- Line 21 - tput sgr0 will turn bold off (and any other changes we may have made).
Line 22 - Place the prompt at the bottom of the screen.
Supply Data: Remember there are 3 ways in which you may supply data to a Bash script:
As command line arguments
- Redirected in as STDIN
- Read interactively during script execution
Your script may use one or a combination of these but should always aim to be the most convenient for the user.
Command line arguments are good as they will be retained in the users history making it easy for them to rerun commands. Command line arguments are also convenient when the script is not run directly by the user (eg, as part of another script or a cron task etc).
Redirected from STDIN is good when your script is behaving like a filter and just modifying or reformatting data that is fed to it.
Reading interactively is good when you don't know what data may be required until the script is already running. eg. You may need to clarify some suspicious or erroneous input. Passwords are also ideally asked for this way so they aren't kept as plain text in the users history.
The command sed can easily allow us to accommodate many formats for input data.
#!/bin/bash
// A date is the first command line argument
clean_date=$( echo $1 | sed 's/[ /:\^#]/-/g' )
echo $clean_date
Final Word
Ok so you've worked through my Bash Scripting guide. Congratulations, you've now acquired some very powerful and useful skills and knowledge. Next you need to gain experience. Your Bash scripting foo is no doubt reasonably good now but it will only get better with practice.
Remember too that this guide is not a complete reference on Bash scripting. I've tried to cover the most important and essential bits. For a lot of you this will be more than enough to automate tasks and make your interaction with Linux much happier. For others, you will want to take things further and there is much more to Bash that you can learn. You now have a solid foundation to launch from and should have no troubles extending your abilities. Either way, I hope your experiences with Linux are, and continue to be, awesome and I wish you the best of luck.
Also, if you have any feedback on this guide, I would be happy to hear from you. It could be a typo you have spotted, or some other error, a bit you feel could be written more clearly, or just that you found it useful. While I can't guarantee to act upon all feedback I do very much appreciate it.
More importantly (especially if you found this tutorial useful), don't keep it a secret. The best way to say thank you is to make sure you share this tutorial on social media (Twitter, Facebook, Google Plus or whatever the hip and happening social platform of today is), give me a shout out on your blog, tell your friends and co-workers, etc.