Skip to content

Introduction to Bash Scripting

Bourne Again Shell

$ bash script.sh <optional arguments>
$ sh script.sh <optional arguments>
$ ./script.sh <optional arguments>

CIDR.sh

$ ./CIDR.sh inlanefreight.com

#!/bin/bash

# Check for given arguments
if [ $# -eq 0 ]
then
    echo -e "You need to specify the target domain.\n"
    echo -e "Usage:"
    echo -e "\t$0 <domain>"
    exit 1
else
    domain=$1
fi

# Identify Network range for the specified IP address(es)
function network_range {
    for ip in $ipaddr
    do
        netrange=$(whois $ip | grep "NetRange\|CIDR" | tee -a CIDR.txt)
        cidr=$(whois $ip | grep "CIDR" | awk '{print $2}')
        cidr_ips=$(prips $cidr)
        echo -e "\nNetRange for $ip:"
        echo -e "$netrange"
    done
}

# Ping discovered IP address(es)
function ping_host {
    hosts_up=0
    hosts_total=0

    echo -e "\nPinging host(s):"
    for host in $cidr_ips
    do
        stat=1
        while [ $stat -eq 1 ]
        do
            ping -c 2 $host > /dev/null 2>&1
            if [ $? -eq 0 ]
            then
                echo "$host is up."
                ((stat--))
                ((hosts_up++))
                ((hosts_total++))
            else
                echo "$host is down."
                ((stat--))
                ((hosts_total++))
            fi
        done
    done

    echo -e "\n$hosts_up out of $hosts_total hosts are up."
}

# Identify IP address of the specified domain
hosts=$(host $domain | grep "has address" | cut -d" " -f4 | tee discovered_hosts.txt)

echo -e "Discovered IP address:\n$hosts\n"
ipaddr=$(host $domain | grep "has address" | cut -d" " -f4 | tr "\n" " ")

# Available options
echo -e "Additional options available:"
echo -e "\t1) Identify the corresponding network range of target domain."
echo -e "\t2) Ping discovered hosts."
echo -e "\t3) All checks."
echo -e "\t*) Exit.\n"

read -p "Select your option: " opt

case $opt in
    "1") network_range ;;
    "2") ping_host ;;
    "3") network_range && ping_host ;;
    "*") exit 0 ;;
esac

Conditional Execution

Script.sh

#!/bin/bash

# Check for given argument
if [ $# -eq 0 ]
then
    echo -e "You need to specify the target domain.\n"
    echo -e "Usage:"
    echo -e "\t$0 <domain>"
    exit 1
else
    domain=$1
fi
#!/bin/bash - Shebang.
if-else-fi - Conditional execution.
echo - Prints specific output.
$# / $0 / $1 - Special variables.
domain - Variables.

If-Only.sh

```sh #!/bin/bash

value=$1

if [ $value -gt "10" ] then echo "Given argument is greater than 10." fi


**If-Elif-Else.sh**

```sh
#!/bin/bash

value=$1

if [ $value -gt "10" ]
then
    echo "Given argument is greater than 10."
elif [ $value -lt "10" ]
then
    echo "Given argument is less than 10."
else
    echo "Given argument is not a number."
fi

Several conditions Script.sh

#!/bin/bash

# Check for given argument
if [ $# -eq 0 ]
then
    echo -e "You need to specify the target domain.\n"
    echo -e "Usage:"
    echo -e "\t$0 <domain>"
    exit 1
elif [ $# -eq 1 ]
then
    domain=$1
else
    echo -e "Too many arguments given."
    exit 1
fi

Exercise script

#!/bin/bash
# Count number of characters in a variable:
#     echo $variable | wc -c

# Variable to encode
var="nef892na9s1p9asn2aJs71nIsm"

for counter in {1..40}
do
    var=$(echo $var | base64)

    if [ $counter -eq "35" ]
    then 
        echo $var | wc -c
    fi
done

bash ./script.sh

Note: above code is not supported by sh, cannot be executed by sh ./script.sh

#!/bin/bash
# Count number of characters in a variable:
#     echo $variable | wc -c

# Variable to encode
var="nef892na9s1p9asn2aJs71nIsm"

counter=0
while [ $counter -le 40 ]
do
    var=$(echo $var | base64)

    counter=$((counter + 1))

    if [ $counter -eq 35 ]; then
         echo "Character count at iteration 35: $(echo $var | wc -c)"
     fi
done

Arguments, Variables, and Arrays

$ chmod +x cidr.sh

Special Variables

Internal Field Separator (IFS)

IFS Description
$# This variable holds the number of arguments passed to the script. (the script's name doesn't count)
$@ This variable can be used to retrieve the list of command-line arguments.
$n Each command-line argument can be selectively retrieved using its position. For example, the first argument is found at $1.
$$ The process ID of the currently executing process.
$? The exit status of the script. This variable is useful to determine a command's success. The value 0 represents successful execution, while 1 is a result of a failure.

Arrays.sh

#!/bin/bash

domains=(www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com www2.inlanefreight.com)

echo ${domains[0]}
#!/bin/bash

domains=("www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com" www2.inlanefreight.com)
echo ${domains[0]}

Comparison Operators

String Operators

Operator Description
== is equal to
!= is not equal to
< is less than in ASCII alphabetical order
> is greater than in ASCII alphabetical order
-z if the string is empty (null)
-n if the string is not null

It is important to note here that we put the variable for the given argument ($1) in double-quotes ("$1"). This tells Bash that the content of the variable should be handled as a string. Otherwise, we would get an error.

#!/bin/bash

# Check the given argument
if [ "$1" != "HackTheBox" ]
then
    echo -e "You need to give 'HackTheBox' as argument."
    exit 1

elif [ $# -gt 1 ]
then
    echo -e "Too many arguments given."
    exit 1

else
    domain=$1
    echo -e "Success!"
fi

String comparison operators "< / >" works only within the double square brackets [[ <condition> ]]. We can find the ASCII table on the Internet or by using the following command in the terminal. We take a look at an example later.

$ man ascii

Integer Operators

Operator Description
-eq is equal to =
-ne is not equal to !=
-lt is less than <
-le is less than or equal to <=
-gt is greater than >
-ge is greater than or equal to >=
#!/bin/bash

# Check the given argument
if [ $# -lt 1 ]
then
    echo -e "Number of given arguments is less than 1"
    exit 1

elif [ $# -gt 1 ]
then
    echo -e "Number of given arguments is greater than 1"
    exit 1

else
    domain=$1
    echo -e "Number of given arguments equals 1"
fi

File Operators

Operator Description
-e if the file exist
-f tests if it is a file
-d tests if it is a directory
-L tests if it is if a symbolic link
-N checks if the file was modified after it was last read
-O if the current user owns the file
-G if the file’s group id matches the current user’s
-s tests if the file has a size greater than 0
-r tests if the file has read permission
-w tests if the file has write permission
-x tests if the file has execute permission
#!/bin/bash

# Check if the specified file exists
if [ -e "$1" ]
then
    echo -e "The file exists."
    exit 0

else
    echo -e "The file does not exist."
    exit 2
fi

Boolean and Logical Operators

We get a boolean value "false" or "true" as a result with logical operators. Bash gives us the possibility to compare strings by using double square brackets [[ <condition> ]]. To get these boolean values, we can use the string operators. Whether the comparison matches or not, we get the boolean value "false" or "true".

#!/bin/bash

# Check the boolean value
if [[ -z $1 ]]
then
    echo -e "Boolean value: True (is null)"
    exit 1

elif [[ $# > 1 ]]
then
    echo -e "Boolean value: True (is greater than)"
    exit 1

else
    domain=$1
    echo -e "Boolean value: False (is equal to)"
fi

Logical Operators

Operator Description
! logical negotation NOT
&& logical AND
|| logical OR
#!/bin/bash

# Check if the specified file exists and if we have read permissions
if [[ -e "$1" && -r "$1" ]]
then
    echo -e "We can read the file that has been specified."
    exit 0

elif [[ ! -e "$1" ]]
then
    echo -e "The specified file does not exist."
    exit 2

elif [[ -e "$1" && ! -r "$1" ]]
then
    echo -e "We don't have read permission for this file."
    exit 1

else
    echo -e "Error occured."
    exit 5
fi

Exercise Script

#!/bin/bash

var="8dm7KsjU28B7v621Jls"
value="ERmFRMVZ0U2paTlJYTkxDZz09Cg"

for i in {1..40}
do
        var=$(echo $var | base64)

        # Check if "var" contains "value" AND has more than 113450 characters
        if [[ "$var" == *"$value"* ]] && [[ $(echo -n "$var" | wc -c) -gt 113450 ]]
        then
            echo $var | tail -c 20   
            # output: 2paTlJYTkxDZz09Cg==
            # echo "Last 20 characters: ${var: -20}"  
            # output: U2paTlJYTkxDZz09Cg==
        fi
done

Arithmetic

Arithmetic Operators

Operator Description
+ Addition
- Subtraction
* Multiplication
/ Division
% Modulus
variable++ Increase the value of the variable by 1
variable-- Decrease the value of the variable by 1

Arithmetic.sh

#!/bin/bash

increase=1
decrease=1

echo "Addition: 10 + 10 = $((10 + 10))"
echo "Subtraction: 10 - 10 = $((10 - 10))"
echo "Multiplication: 10 * 10 = $((10 * 10))"
echo "Division: 10 / 10 = $((10 / 10))"
echo "Modulus: 10 % 4 = $((10 % 4))"

((increase++))
echo "Increase Variable: $increase"

((decrease--))
echo "Decrease Variable: $decrease"
#!/bin/bash

htb="HackTheBox"

# calculate the length of the variable
echo ${#htb}

Input and Output

Output Control

The tee utility ensures that we see the results we get immediately and that they are stored in the corresponding files.

Flow Control - Loops

For Loops

for variable in file1 file2 file3
do
    echo $variable
done
for ip in "10.10.10.170 10.10.10.174 10.10.10.175"
do
    ping -c 1 $ip
done

While Loops

WhileBreaker.sh

#!/bin/bash

counter=0

while [ $counter -lt 10 ]
do
  # Increase $counter by 1
  ((counter++))
  echo "Counter: $counter"

  if [ $counter == 2 ]
  then
    continue
  elif [ $counter == 4 ]
  then
    break
  fi
done

Until Loops

There is also the until loop, which is relatively rare. Nevertheless, the until loop works precisely like the while loop, but with the difference:

  • The code inside a until loop is executed as long as the particular condition is false.

The other way is to let the loop run until the desired value is reached. The "until" loops are very well suited for this. This type of loop works similarly to the "while" loop but, as already mentioned, with the difference that it runs until the boolean value is "False."

#!/bin/bash

counter=0

until [ $counter -eq 10 ]
do
  # Increase $counter by 1
  ((counter++))
  echo "Counter: $counter"
done

Exercise script

#!/bin/bash

# Decrypt function
function decrypt {
    MzSaas7k=$(echo $hash | sed 's/988sn1/83unasa/g')
    Mzns7293sk=$(echo $MzSaas7k | sed 's/4d298d/9999/g')
    MzSaas7k=$(echo $Mzns7293sk | sed 's/3i8dqos82/873h4d/g')
    Mzns7293sk=$(echo $MzSaas7k | sed 's/4n9Ls/20X/g')
    MzSaas7k=$(echo $Mzns7293sk | sed 's/912oijs01/i7gg/g')
    Mzns7293sk=$(echo $MzSaas7k | sed 's/k32jx0aa/n391s/g')
    MzSaas7k=$(echo $Mzns7293sk | sed 's/nI72n/YzF1/g')
    Mzns7293sk=$(echo $MzSaas7k | sed 's/82ns71n/2d49/g')
    MzSaas7k=$(echo $Mzns7293sk | sed 's/JGcms1a/zIm12/g')
    Mzns7293sk=$(echo $MzSaas7k | sed 's/MS9/4SIs/g')
    MzSaas7k=$(echo $Mzns7293sk | sed 's/Ymxj00Ims/Uso18/g')
    Mzns7293sk=$(echo $MzSaas7k | sed 's/sSi8Lm/Mit/g')
    MzSaas7k=$(echo $Mzns7293sk | sed 's/9su2n/43n92ka/g')
    Mzns7293sk=$(echo $MzSaas7k | sed 's/ggf3iunds/dn3i8/g')
    MzSaas7k=$(echo $Mzns7293sk | sed 's/uBz/TT0K/g')

    flag=$(echo $MzSaas7k | base64 -d | openssl enc -aes-128-cbc -a -d -salt -pass pass:$salt)
}

# Variables
var="9M"
salt=""
hash="VTJGc2RHVmtYMTl2ZnYyNTdUeERVRnBtQWVGNmFWWVUySG1wTXNmRi9rQT0K"

# Encoding "var" 28 times using base64
for i in {1..28}
do
    # cannot use echo -n $var | base64
    var=$(echo $var | base64)
done

# Assign salt when i reaches 28
salt=$(echo -n $var | wc -c)

echo "The salt value is $salt."

# Check if $salt is empty
if [[ ! -z "$salt" ]]
then
    decrypt
    echo $flag
else
    exit 1
fi

echo "string" | wc -c counts the exact number of characters in the string returned by echo, that is "string" plus a line break appended by echo, so 7 in that case. ${#var} returns the exact number of characters contained in the var variable. You can obtain the same behavior by specifying the -n flag to echo, which gets rid of the trailing \n.

$ echo 9M | base64
OU0K

$ echo -n 9M | base64
OU0=

The sed (Stream Editor) command is used for text processing in Unix/Linux. It allows you to:

  • Search and replace text
  • Filter and modify strings
  • Delete or insert lines
  • Extract specific parts of text

Flow Control - Branches

Syntax - Switch-Case

case <expression> in
    pattern_1 ) statements ;;
    pattern_2 ) statements ;;
    pattern_3 ) statements ;;
esac

Functions

Method 1 - Functions

function name {
    <commands>
}

Method 2 - Functions

name() {
    <commands>
}

Parameter Passing

PrintPars.sh

#!/bin/bash

function print_pars {
    echo $1 $2 $3
}

one="First parameter"
two="Second parameter"
three="Third parameter"

print_pars "$one" "$two" "$three"

Return Values

When we start a new process, each child process (for example, a function in the executed script) returns a return code to the parent process (bash shell through which we executed the script) at its termination, informing it of the status of the execution. This information is used to determine whether the process ran successfully or whether specific errors occurred. Based on this information, the parent process can decide on further program flow.

Return Code Description
1 General errors
2 Misuse of shell builtins
126 Command invoked cannot execute
127 Command not found
128 Invalid argument to exit
128+n Fatal error signal "n"
130 Script terminated by Control-C
255\* Exit status out of range

To get the value of a function back, we can use several methods like return, echo, or a variable. In the next example, we will see how to use "$?" to read the "return code," how to pass the arguments to the function and how to assign the result to a variable.

Return.sh

#!/bin/bash

function given_args {

        if [ $# -lt 1 ]
        then
                echo -e "Number of arguments: $#"
                return 1
        else
                echo -e "Number of arguments: $#"
                return 0
        fi
}

# No arguments given
given_args
echo -e "Function status code: $?\n"

# One argument given
given_args "argument"
echo -e "Function status code: $?\n"

# Pass the results of the funtion into a variable
content=$(given_args "argument")

echo -e "Content of the variable: \n\t$content"

Debugging

$ bash -x CIDR.sh

# display the output in more details
$ bash -x -v CIDR.sh