---
# System prepended metadata

title: Exploring Bash Scripting

---

# Exploring Bash Scripting

### checking the specific bash version
```bash
bash --version
```

### checking the environment variables

*bash Varibles https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html*

```bash
env 
env ${ENVIRONMENT_VARIABLES}
```

### listing processes
```bash
ps -e -f
```
*ps stands for list processes*
*-e stands for every process*
*-f stands for full listing*

### checking for available space 
```bash
df --human-readable
df -h
```
## Elements of a Bash Script

*Using google standard Guide https://google.github.io/styleguide/shellguide.html*

### The shebang line
```bash
#!/bin/bash
```

### Optional Arguments and what they do
```bash
#!/bin/bash -x
```
*Prints all the commands and arguments as they are present in the script, very useful for debugging*

```bash
#!/bin/bash -r
```
*Creates a restricted bash shell*

```bash
bash -r myScript.sh
```
*Passing an argument to the bash interpreter*

### Writing comments in bash
```bash
#!/bin/bash

# This is my first script
```

### A simple script that prints Hello World!
```bash
#!/bin/bash

echo "Hello World!"
```

### Debugging scripts
```bash
bash -n myscript.sh
```
*This performs a dry run without executing any codes but highlights and print syntax errors on the code*

```bash
bash -x myscript.sh
```
*executes the codes in realtime using verbose mode and highlights the error while running and helps to debug*

```bash
#!/bin/bash

set -x

--snip--

set +x
```
*This performs point to point debugging, where set is like an on and off switch*

### A script that creates a directory and files within it and list the files
```bash
#!/bin/bash

# Create the Directory
mkdir myDirectory

# Create two files in the Directory
touch myDirectory/file1
touch myDirectory/file2

ls -l myDirectory

echo "......end of script"
```

### Variables in Bash

*The following rules govern the naming of bash variables:
• They can include alphanumeric characters.
• They cannot start with a number.
• They can contain an underscore (_).
• They cannot contain whitespace*

*In bash, variables are considered type strings by default.

### Assigning and Accessing Variables
```bash
# assigning the variable
book="Black hat bash"

# reading the value of the variable using ${variable_name}
echo "The name of the book is ${book}"

#reading the value of the variable using $Variable_name
echo "The name of the book is $book"
```
### Assigning output of a command to a variable
```bash
root_directory=$(ls -ld /)

echo "${root_directory}"
```
### Unassigning Variables
*Unassigning variables is done using the unset command*
```bash
unset book

# This removes the value set in the vook variable initially
```

### Scoping Variables

#### Global Variables are those variables whose values available to the entire program.

#### Local variables are those whose values are available within the local scope.

```bash
#!/bin/bash

PUBLISHER="No Starch Press"

print_name(){
	local name
	name="Black Hat Bash"
	echo "${name} by ${PUBLISHER}"
}

print_name

echo "Variable ${name} will not be printed because it is a local variable, but variable ${PUBLISHER} will be printed because it is a global variable."

```

```bash
$ ./printName.sh

Black Hat Bash by No Starch Press

Variable  will not be printed because it is a local variable, but variable No Starch Press will be printed because it is a global variable.

```

### Arithmetic Operators

*full details on operators are available here https://tldp.org/LDP/abs/html/ops.html*

<pre>
Operator           Description
   +               Addition
   -               Subtraction
   *               Multiplication
   /               Division
   %               Modulo
   +=              Incrementing by a constant
   -=              Decrementing by a constant
</pre>

*you can perform arithmetic operation using different methods*
### using the let Command
```bash=
let result="5 * 10"

echo "$result" # prints 50
```
### using the double parenthesis command
```bash
result=$((5 * 10))

echo "${result}" # prints 5
```

### using the expr command
```bash
complete=$(expr 20 + 40)

echo "${complete}" # prints 60
```

### more thins to do with expr
```bash=
#!/bin/bash

# Get the length of a specific string
expr length "string" # prints 5

# Get the substring of a string with a specific length 
# expr substr "string" from <start lenght> to <length after start>

expr substr "My test string" 4 4 # prints test

# In modern scripting, you can also do this
string="my test string"
# echo ${string:position:length}
echo ${string:3:4} #prints test

# Match a specific substring against an anchored pattern
*syntax*
# expr match <string/variable> <pattern>

# Match using regular expression
expr match "abc123xyz" '[a-z]*' # prints 3 (matching the a,b and c in the string)

# Match using a substring
expr match "hello world" 'hello' # prints 5

expr match "hello world" : 'hello' # prints 5

# Get the first character position from a specific set in a string
*syntax*
# expr index "string" "char"

expr index "my name is StingRay Okafor" "o" # prints 25 as thats where the character small o was found.

# find the position of any character in a set
expr index "hello world" "aeiou" # prints 2, as e was first found in the second position of hello
```

### Arrays in bash
```bash

#!/bin/bash

# Sets an array
IP_ADDRESSES=(192.168.1.1 192.168.1.2 192.168.1.3)

# Prints all elements in the array
echo "Print all IP Addresses in the list"
echo "${IP_ADDRESSES[*]}"

# Prints only the first element in the array
echo "Print only the first IP Address in the list"
echo "${IP_ADDRESSES[0]}"
```

### Deleting elements from an Array using unset
```bash
unset IP_ADDRESSES[1]
```

### Swapping array values with some other value
```bash
IP_ADDRESSES[1]=192.168.1.10
```

### Streams

*Streams are files that act as a communication channel between a program and its environment. Bash has 3 standard streams*

<table>
    <thead>
        <tr>
            <th>Stream Name</th>
            <th>Description</th>
            <th>File Descriptor Number</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Standard Input (stdin) </td>
            <td> Data coming into a program as input</td>
            <td>0</td>
        </tr>
        <tr>
            <td>Standard Output (stdout) </td>
            <td> Data coming out of a program as an output</td>
            <td>1</td>
        </tr>
        <tr>
            <td>Standard Error (stderr)</td>
            <td>Errors coming out of a program</td>
            <td>2</td>
        </tr>
    </tbody>
</table>

### Control Operators in Bash
<em>These are tokens that perform operations in bash</em>

<table>
    <thead>
        <tr>
            <th>Operator</th>
            <th>Descriptions</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>&</td>
            <td>Sends a command to the background</td>
        </tr>
        <tr>
            <td>&&</td>
            <td>Logical AND operator</td>
        </tr>
        <tr>
            <td>( )</td>
            <td>Used to group commands together</td>
        </tr>
        <tr>
            <td>;</td>
            <td>Used as a list terminator A command following the terminator will run
after the preceding command has finished, regardless of whether it
evaluates to true or not</td>
        </tr>
        <tr>
            <td>;;</td>
            <td>ends a case statement</td>
        </tr>
        <tr>
            <td>|</td>
            <td>Redirects the output of a command as an input of another command</td>
        </tr>
        <tr>
            <td>||</td>
            <td>Logical OR Operator</td>
        </tr>
    </tbody>
</table>

### Logical AND Operator
```bash
sudo apt update && sudo apt upgrade -y
```
*The second command only executes if the first command is successful*

### Parenthesis Operator
```bash
(ps; df --human-readable)
```

*This runs the processes command, then move to run the df command which prints the available disc spaces in human readable format*

### Semi-colon operator ;
```bash
ls;ps;whoami
```

*This lists available files and directories in the current directory, then prints the processes running and move on to print the current user*

```bash
# print the current working directory, list the contents, run the process command and print the current user

pwd;ls;ps;whoami

```

### Chaining commands with the OR operator ||
```bash
pwd || echo "the pwd command failed"
```
*The command above prints the working directory and ignores the other command; hence, once one of each side works, the other is ignored*

## Redirecting Operators
*The three standard streams {stdin, stdout, stderr}, highlighted earlier are redirected from one program to another.

<table>
    <thead>
        <tr>
            <th>Operator</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>></td>
            <td>Redirects stdout to a file</td>
        </tr>
        <tr>
            <td>>></td>
            <td>Redirects stdout to a file by appending it to an existing content</td>
        </tr>
        <tr>
            <td>>& or &></td>
            <td>Redirects stdout and stderr to a file</td>
        </tr>
        <tr>
            <td>&>></td>
            <td>Redirects stdout and stderr to a file by appending it to an existing file</td>
        </tr>
        <tr>
            <td> < </td>
            <td>Redirects input to a command</td>
        </tr>
        <tr> 
            <td> << </td>
            <td>Called a here document, or heredoc, redirects multiple input lines
to a command</td>
        </tr>
        <tr>
            <td> | </td>
            <td>Redirects output of a command as input to another command (Pipe)</td>
        </tr>
    </tbody>
</table>

### Redirection Operators Application
* > operator*
```bash
echo "This is my first redirection" > output.txt

# The output.txt file will contain the string above
```

* >> operator*
```bash
echo "This s a second line in my redirections" >> output.txt

# This prints the above string in a new line below the previous string in the output.txt file
```

* &> operator*
```bash
# standard output and standard error redirection

ls -l / &> stdout_and_stderr.txt

cat stdout_and_stderr.txt

## to append outputs to the stdout_and_stderr.txt file we use the ampersand and double chevron

ls -l / &>> stdout_and_stderr.txt
```

* 1> and 2> operators*
```bash
# Sending the standard output to one file and the standard error ot another file we do this

ls -l / 1> stdout.txt 2> stderr.txt

# using the error 2> command to log errors and send them to a file during runtime

lzl 2> error.txt
# lzl is not a real linux command hence it will throw an error, which is sent to the error.txt file.

$ cat error.txt # displays the error from the lzl command
```
### standard input stream using the < operator
```bash
cat < output.txt 
# note that the file could be in any accurate format.
# this is mostly used to for a single file
```

### standard input stream using the << operator

```bash
# mostly used to redirect multiple lines to a command
#!/bin/bash

cat << EOF
    This is my first rodeo
    However, I am still thinking of my second
    And maybe third
EOF

# save this to a file named test_mul_red.sh
# make it executable

chmod u+x test_mul_red.sh

# run the script 
./test_mul_red.sh

```

### Pipe redirect operator |
```bash
ls -l / | grep "bin"

# this command lists in the long format the root directory and searches for the folder with "bin" as part of the name and outputs them.

```

## Positional Arguments
```bash
#!/bin/bash

# This script will png any address provided as an argument

# ${0} is assigned by default to the script name
# ${1} takes the first positional argument, which is either a domain name, or an IP Address

SCRIPT_NAME="${0}"
TARGET="${1}"

echo "Running the script ${SCRIPT_NAME}..."
echo "Pinging the target: ${TARGET}..."
ping "${TARGET}"


```
*Save the script as ping_with_arg.sh

```bash
chmod u+x ping_with_arg.sh
```
```bash
# Run the scrip with
./ping_with_script.sh nostarch.com

```

<pre>
The next script takes an input parameter from the user and pings the domain 10 times and return the result
</pre>

```bash
#!/bin/bash

# This script tells you what to do

echo "After seeing the script name below, you will be asked to input an argument"

# Assigning the first parameter to the script.
SCRIPT_NAME="${0}"

# Running this should print the script name

echo "The script name is $SCRIPT_NAME"
sleep 2

# Ask the user to input an argument
read -p "Enter the target domain name you intend to ping: " TARGET

# Check if an input was provided by the user
if [[ -z "${TARGET}" ]]; then
	echo "No target domain proided. Exiting ....."
	exit 1
fi


# Display a message and wait

echo "Pinging ${TARGET}..."
sleep 2

# Execute the ping command
ping -c 10 "${TARGET}"

# Exit the script
echo "Ping completed. Exiting..."
exit 0
```

### Detailed pinging commands
<pre>- <font color="#5EBDAB">Ping host:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">ping </font>host

 - <font color="#5EBDAB">Ping a host only a specific number of times:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">ping -c </font>count<font color="#FEA44C"> </font>host

 - <font color="#5EBDAB">Ping host, specifying the interval in seconds between requests (default is 1 se</font>
<font color="#5EBDAB">cond):</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">ping -i </font>seconds<font color="#FEA44C"> </font>host

 - <font color="#5EBDAB">Ping host without trying to lookup symbolic names for addresses:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">ping -n </font>host

 - <font color="#5EBDAB">Ping host and ring the bell when a packet is received (if your terminal support</font>
<font color="#5EBDAB">s it):</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">ping -a </font>host

 - <font color="#5EBDAB">Also display a message if no response was received:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">ping -O </font>host

 - <font color="#5EBDAB">Ping a host with specific number of pings, timeout (-W) for each reply, and tot</font>
<font color="#5EBDAB">al time limit (-w) of the entire ping run:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">ping -c </font>count<font color="#FEA44C"> -W </font>seconds<font color="#FEA44C"> -w </font>seconds<font color="#FEA44C"> </font>host
</pre>

### Accessing all arguments in a script is done with $@

```bash
#!/bin/bash

# This shows all the arguments passed
echo "The arguments are: $@"
```
### Accessing the total number of arguments in a script is done with $#

```bash
#!/bin/bash

# This tells us the total number of arguments passed
echo "The total number of arguments is: $#"
```

### Variables related to positional arguments
<table>
    <thead>
        <tr>
            <th>Variable</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>$0</td>
            <td>The name of the script file</td>
        </tr>
        <tr>
            <td>$1, $2, $3</td>
            <td>Positional arguments</td>
        </tr>
        <tr>
            <td>$#</td>
            <td>Number of passed positional arguments</td>
        </tr>
        <tr>
            <td>$*</td>
            <td>All positional arguments</td>
        </tr>
        <tr>
            <td>$@</td>
            <td>All positional arguments, where each argument is individually quoted</td>
        </tr>
    </tbody>
</table>

### The difference between $@ and $* in a loop
<pre>
Create a script named test_args.sh

copy and paste the script below

Chmod u+x test_args.sh

# run the command

bash test_args.sh "1" "2" "3" "4" "5"

Observe how $@ displays on each new line once the script is run, while $* displays on one line
</pre>

```bash
#!/bin/bash

# Change "$@" to "$*" to observe the behaviour
echo "Below shows the \$@ display"
for args in "$@"; do
	echo "${args}"
done

echo "Below shows the \$* display"

for args in "$*"; do
	echo "${args}"
done
```
*Output*
<pre>
Below shows the $@ display
1
2
3
4
5
Below shows the $* display
1 2 3 4 5
</pre>

### Input prompting
<pre>
A script that asks users for inputing their first name and last name, stores it as a variable using "read -r", and prints an output
</pre>

```bash
#!/bin/bash

# Takes input from the user with "read -r", and assigns it to a variable 

echo "What is your first name?"
read -r firstname

echo "What is your last name?"
read -r lastname

echo "Your first name is ${firstname} and your last name is ${lastname}"

```

### Everything you can do with the read command in bash
<pre>- <font color="#5EBDAB">Store data that you type from the keyboard:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">read </font>variable

 - <font color="#5EBDAB">Store each of the next lines you enter as values of an array:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">read -a </font>array

 - <font color="#5EBDAB">Specify the number of maximum characters to be read:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">read -n </font>character_count<font color="#FEA44C"> </font>variable

 - <font color="#5EBDAB">Assign multiple values to multiple variables:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">read </font>_ variable1 _ variable2<font color="#FEA44C"> &lt;&lt;&lt; &quot;</font>The surname is Bond<font color="#FEA44C">&quot;</font>

 - <font color="#5EBDAB">Do not let backslash (\) act as an escape character:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">read -r </font>variable

 - <font color="#5EBDAB">Display a prompt before the input:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">read -p &quot;</font>Enter your input here: <font color="#FEA44C">&quot; </font>variable

 - <font color="#5EBDAB">Do not echo typed characters (silent mode):</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">read -s </font>variable

 - <font color="#5EBDAB">Read stdin and perform an action on every line:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">while read line; do </font>echo|ls|rm|...<font color="#FEA44C"> &quot;$line&quot;; done &lt; </font>/dev/stdin|path/to/file|...
</pre>

### Working with dates in Bash
<pre> - <font color="#5EBDAB">Display the current date using the default locale&apos;s format:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">date +%c</font>

 - <font color="#5EBDAB">Display the current date in UTC, using the ISO 8601 format:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">date -u +%Y-%m-%dT%H:%M:%S%Z</font>

 - <font color="#5EBDAB">Display the current date as a Unix timestamp (seconds since the Unix epoch):</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">date +%s</font>

 - <font color="#5EBDAB">Convert a date specified as a Unix timestamp to the default format:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">date -d @</font>1473305798

 - <font color="#5EBDAB">Convert a given date to the Unix timestamp format:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">date -d &quot;</font>2018-09-01 00:00<font color="#FEA44C">&quot; +%s --utc</font>

 - <font color="#5EBDAB">Display the current date using the RFC-3339 format (YYYY-MM-DD hh:mm:ss TZ):</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">date --rfc-3339 s</font>

 - <font color="#5EBDAB">Set the current date using the format MMDDhhmmYYYY.ss (YYYY and .ss are optiona</font>
<font color="#5EBDAB">l):</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">date </font>093023592021.59

 - <font color="#5EBDAB">Display the current ISO week number:</font>
<font color="#5EBDAB">   </font><font color="#FEA44C">date +%V</font>
</pre>


