Skip to main content

Basic Script

#!/bin/bash

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

<SNIP>
  • #!/bin/bash selects the interpreter.
  • if-else-fi handles conditional execution.
  • echo prints output.
  • $#, $0, and $1 are special variables.
  • domain=$1 stores the first user argument.

Shebang

The shebang is the first line of a script and starts with #!. It tells the system which interpreter should run the file.
#!/bin/bash
Other common examples:
#!/usr/bin/env python
#!/usr/bin/env perl

Conditionals

Use if, elif, else, and fi to branch based on conditions.
#!/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
Dudji@htb[/htb]$ bash if-elif-else.sh HTB
if-elif-else.sh: line 5: [: HTB: integer expression expected
if-elif-else.sh: line 8: [: HTB: integer expression expected
Given argument is not a number.

Arguments and Variables

Bash supports positional arguments directly.
  • $0 is the script name.
  • $1 to $9 are the first nine positional arguments.
  • $# is the number of arguments.
Dudji@htb[/htb]$ ./script.sh ARG1 ARG2 ARG3 ... ARG9

ASSIGNMENTS:
$0      $1   $2   $3 ...   $9

Special Variables

Special variables use the Internal Field Separator (IFS) to split arguments.
Special VariableDescription
$#Number of arguments passed to the script.
$@Full list of command-line arguments.
$nArgument by position, for example $1.
$$PID of the current process.
$?Exit status of the last command. 0 means success.

Variables

Assignments do not use $, and there must be no spaces around =.
domain=$1
echo "$domain"
Bash treats variable content mostly as strings unless used in arithmetic. Variables are global by default unless declared with local.
Dudji@htb[/htb]$ variable = "this will result with an error."
command not found: variable

Dudji@htb[/htb]$ variable="Declared without an error."
Dudji@htb[/htb]$ echo $variable
Declared without an error.

Arrays

Arrays store multiple values under one variable name, indexed from 0.
#!/bin/bash

domains=(www.inlanefreight.com ftp.inlanefreight.com vpn.inlanefreight.com www2.inlanefreight.com)
echo ${domains[0]}
Quoted values stay together as one array element:
#!/bin/bash

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

Arithmetic

Use arithmetic expansion $((...)) and increment/decrement operators for math.
OperatorDescription
+Addition
-Subtraction
*Multiplication
/Division
%Modulus
variable++Increase by 1
variable--Decrease by 1
#!/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"
${#variable} returns string length:
#!/bin/bash

htb="HackTheBox"
echo ${#htb}

Input and Output

Input Control

Use read when the script should pause for user input.
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

Output Control

Use tee when you want to both display output and save it to a file.
netrange=$(whois $ip | grep "NetRange\|CIDR" | tee -a CIDR.txt)
hosts=$(host $domain | grep "has address" | cut -d" " -f4 | tee discovered_hosts.txt)
-a appends instead of overwriting.
Dudji@htb[/htb]$ cat discovered_hosts.txt CIDR.txt

165.22.119.202
NetRange:       165.22.0.0 - 165.22.255.255
CIDR:           165.22.0.0/16

Flow Control - Branches

Branches let the script choose one path over another. In Bash, the main branch constructs are if-else and case.

Case Statements

case compares one expression against exact patterns.
case <expression> in
    pattern_1 ) statements ;;
    pattern_2 ) statements ;;
    pattern_3 ) statements ;;
esac
Example:
case $opt in
    "1") network_range ;;
    "2") ping_host ;;
    "3") network_range && ping_host ;;
    "*") exit 0 ;;
esac

Functions

Functions keep scripts shorter and easier to reuse. Define them before the first call because Bash reads from top to bottom.
function name {
    <commands>
}
name() {
    <commands>
}
Example:
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
}

Parameter Passing

Functions use their own positional parameters, just like scripts.
#!/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

Functions return status codes, and $? reads the last one.
Return CodeDescription
1General errors
2Misuse of shell builtins
126Command invoked cannot execute
127Command not found
128Invalid argument to exit
128+nFatal error signal n
130Script terminated by Control-C
255Exit status out of range
#!/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
}

given_args
echo -e "Function status code: $?\n"

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

content=$(given_args "argument")
echo -e "Content of the variable: \n\t$content"

Flow Control - Loops

Loops repeat work until input is exhausted or a condition changes.
  • for loops iterate over items.
  • while loops run while a condition is true.
  • until loops run while a condition is false.

For Loops

for variable in 1 2 3 4
do
    echo $variable
done
for variable in file1 file2 file3
do
    echo $variable
done
for ip in 10.10.10.170 10.10.10.174
do
    ping -c 1 $ip
done
One-line form:
Dudji@htb[/htb]$ for ip in 10.10.10.170 10.10.10.174;do ping -c 1 $ip;done

While Loops

while loops need a counter or changing condition so they do not run forever.
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
continue skips to the next iteration and break exits the loop.
#!/bin/bash

counter=0

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

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

Until Loops

until is the inverse of while: it runs until the condition becomes true.
#!/bin/bash

counter=0

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

Comparison Operators

Bash operators are usually grouped into string, integer, file, and logical checks.

String Operators

OperatorDescription
==is equal to
!=is not equal to
<is less than in ASCII order
>is greater than in ASCII order
-zstring is empty
-nstring is not empty
Quote strings like "$1" to avoid parsing issues.
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
    echo -e "Success!"
fi
< and > string comparisons work inside [[ ... ]].
Dudji@htb[/htb]$ man ascii

Integer Operators

OperatorDescription
-eqis equal to
-neis not equal to
-ltis less than
-leis less than or equal to
-gtis greater than
-geis greater than or equal to
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
    echo -e "Number of given arguments equals 1"
fi

File Operators

OperatorDescription
-efile exists
-fregular file
-ddirectory
-Lsymbolic link
-Nmodified since last read
-Oowned by current user
-Ggroup matches current user
-ssize greater than 0
-rreadable
-wwritable
-xexecutable
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

Use [[ ... ]] for boolean-style checks.
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
    echo -e "Boolean value: False (is equal to)"
fi
OperatorDescription
!logical NOT
&&logical AND
||logical OR
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 occurred."
    exit 5
fi

Debugging

Bash debugging is usually done with -x and -v.
  • bash -x script.sh shows each command as it executes.
  • bash -x -v script.sh also shows the code as Bash reads it.
Dudji@htb[/htb]$ bash -x CIDR.sh

+ '[' 0 -eq 0 ']'
+ echo -e 'You need to specify the target domain.\n'
You need to specify the target domain.
Dudji@htb[/htb]$ bash -x -v CIDR.sh

#!/bin/bash
if [ $# -eq 0 ]
then
    echo -e "You need to specify the target domain.\n"
    exit 1
fi