In this tutorial, we will see how we can write basic POSIX compatible scripts. Scripts are very necessary in system administration and automation because once they are written, they make many tasks much easier.
Each script starts with "the shebang". It specifies the path of the interpreter. For example:
These are the most common shebangs you will see. The difference between them is that the first script uses Bash as its interpreter, while the other is POSIX compliant and will run with any POSIX compliant shell like Bash.
/bin/sh scripts have the advantage of running on most systems, while
/bin/bash scripts only work with Bash, but can utilize some useful Bash extensions. This tutorial focuses on POSIX-compliant scripts, so in theory we can run our script with a shell like Dash instead of Bash.
In the following examples, the shebang will be omitted, but it should always be present in your actual scripts.
A script is basically nothing more than a list of commands:
echo "Hello World" echo "This is a tutorial about scripting"
Comments are ignored by the interpreter and can be used to clarify issues. They can be created by writing a
# before the message:
## This comment will be ignored by the interpreter ## The following command will echo something echo Hello World
Variables hold values and are created like this:
var="A variable" second_var="A second variable"
The contents of a variable can be accessed this way:
## Prints "A variable" echo "$var"
The inverted commas (
") can be omitted in some cases, but it is recommended to add them to avoid problems.
The value of a variable can be the output (STDOUT) of a command.
## The value of "var" is the output of the command "cat file". var=$(cat file)
If statements are an important feature of scripting. Let's take a look at them.
Each command has an exit code. This is either 0 (everything went well) or something other than 0 (something went wrong). In If statements, our condition is a command. If its exit code is 0, the if statement is executed, if not, it is skipped.
if <COMMAND> then <COMMAND> <COMMAND> ... fi
So let's take a look at this example:
if ping -c 1 -W 3 220.127.116.11 then echo "There is an active internet connection" fi
If the ping to
18.104.22.168 is successful, the message will be displayed. We can also display another message if there is no internet connection. So let's add an
if ping -c 1 -W 3 22.214.171.124 then echo "There is an active internet connection" else echo "There is no active internet connection" fi
There is no program available for comparing numbers. We will need to use our shell features:
first_var="1" second_var="2" if [ $first_var -gt $second_var ] then echo "The first number is greater than the second" else echo "The first number is not greater than the second" fi
[ $first_var -gt $second_var ] is actually nothing more than a command provided by our shell. Its exit code is 0 if
first_var is greater than
second_var, or 1 if this isn't the case. So in general,
-gt checks whether the first variable is greater than the second. Of course, there are other checks as well:
-gt: First number is greater than the second number.
-ge: First number is greater than or equal to the second number.
-lt: First number is smaller than the second number.
-le: First number is smaller than or equal to the second number.
-eq: Both numbers are the same.
When comparing two numbers, there can be three cases:
In a script, we can check these conditions with an
elif and an
first_var="1" second_var="2" if [ $first_var -gt $second_var ] then echo "The first number is greater than the second" elif [ $first_var -eq $second_var ] echo "Both numbers are the same" else echo "The second number is greater than the first" fi
If statements can be negated using
!, which means that the if statement will be executed if it actually wouldn't, and the other way around:
first_var="1" second_var="2" ## This if statement will be executed if the first number is not greater than the second number. if ! [ $first_var -gt $second_var ] then echo "The second number is greater than the first or both numbers are the same" fi
Strings can be compared like this:
first_var="hello" second_var="hello" if [ "$first_var" = "$second_var" ] then echo "The first string is the same as the second" fi
Note: Inverted commas (
") are not needed when numbers are compared, but they must be used when strings are compared.
Let's compare the content of two files:
if [ "$(cat file1)" = "$(cat file2)" ] then echo "file1 and file2 have the same contents" fi
Checking if a variable exists:
## This If statement will be executed because var was not declared if [ -z "$var" ] then echo "Variable 'var' does not exist" fi
Checking if a file exists:
if [ -f "file" ] then echo "The file with the name 'file' does exist" fi
We can use the
sleep command to add a delay to our script (in seconds):
echo "Hello" ## Let's wait 3 seconds sleep 3 echo "World"
Let's see how we can add, subtract, multiply, and divide two numbers:
first_var="10" second_var="5" ## Addition result_addition=$(($first_var + $second_var)) echo "$first_var + $second_var = $result_addition" ## Subtraction result_subtraction=$(($first_var - $second_var)) echo "$first_var - $second_var = $result_subtraction" ## Multiplication result_multiplication=$(($first_var * $second_var)) echo "$first_var * $second_var = $result_multiplication" ## Division result_division=$(($first_var / $second_var)) echo "$first_var / $second_var = $result_division"
Please note that there are no floating point numbers in Bash. This means that the quotient of 3 and 4 (
$((3 / 4))) is 0. Instead, a tool like
bc is needed.
While loops are executing commands when the exit code of a certain command is 0.
while <COMMAND> do <COMMAND> <COMMAND> ... done
Infinite While statements are often needed to execute something periodically. Let's take a look at them:
while true do sleep 2 echo "Hello World" done
In this case,
true is again nothing more than a command provided by our shell, which always has the exit code 0. Statements often contain sleep statements so that they aren't executed too quickly.
Also, we can utilize While statements to execute a command several times (in the example below 5 times):
i=0 while [ $i -lt 5 ] do echo "Current iteration: $i" i=$(($i + 1)) done
First the script must be made executable:
$ chmod +x my-script
Then we can run it:
If the script is in our current working directory, it can be executed like this:
In this tutorial, you've learned how to write basic POSIX compliant scripts including commands, comments, variables, If statements, While statements, basic mathematical operations and delays.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
By making a contribution to this project, I certify that:
The contribution was created in whole or in part by me and I have the right to submit it under the license indicated in the file; or
The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same license (unless I am permitted to submit under a different license), as indicated in the file; or
The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it.
I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the license(s) involved.