Home OSCP-5: Bash Scripting
Post
Cancel

OSCP-5: Bash Scripting

Chapter 5: Bash Scripting


Learning to write scripts in Bash is really important for automatizing tasks. A Bash Script is a file that contains commands and they will be executed as if they had been typed directly at a terminal prompt.

A bash script starts with #!/bin/bash:

  • #!: This is the shebang, it is ignored by the Bash interpreter but when the file is used as an executable, the loader executes the specified interpreter passing the contents as an argument.
  • /bin/bash: This is the absolute path to the interpreter that will run the script.

Then we would add all the bash commands that we want to execute.

Variables

I won’t get into much detail about what is a variable since if you have basic programming knowledge you know what’s their usage. When using Bash, a variable can be declared in different ways, but the easier way is using the = sign, for example:

1
variable=TEST

It’s important to mention that there are not any spaces between the sign.

When we want to reference (expand) a variable we need to precede the name of the variable with the $ sign:

1
2
3
4
echo variable
variable
❯ echo $variable
TEST

If we want to declare variables in a multi-value way, we need to enclose the content using single quotes (') or double quotes ("). However, Bash treats them differently:

  • Single quotes: All characters are interpreted literally.
  • Double Quotes: All characters are interpreted literally except from $, ` and \, meaning that we can expand variables when declaring another variable. In the next example we can see the difference:
    1
    2
    3
    4
    5
    6
    7
    
    test=World
    ❯ single='Hello $test'echo $single
    Hello $testdouble="Hello $test"echo $double
    Hello World
    

    Another interesting thing that we can do is to assing to a variable the output of another command. To do this we need to use parentheses (()) preceded by a $. Alternatively we can use `. This changes are made in a subshell, so they won’t change the values in the master process unles we directly assign them.

    1
    2
    3
    4
    5
    6
    
    test=$(whoami)echo $test
    adri
    ❯ $(test=changed)echo $test
    adri
    

Arguments

When creating a script, you can work with arguments passed when the script is executed. Inside the script we can use several variables that can be really useful:

Variable NameDescription
$0The name of the Bash script
$1-$9The first 9 arguments passed to the Bash script
$#Number of arguments passed to the Bash script
$@All arguments passed to the Bash script
$?The exit status of the last executed run process
$$The process ID of the current script
$USERThe username of the user running the script
$HOSTNAMEThe hostname of the machine
$RANDOMA random number
$LINENOThe current line number in the script

Interactive User Input

We can read input that the user provide in an interactive way. To do this, we will use the read command. The read input can be used along with the -p and -s options. The first one allows you to specify a prompt and the last one makes the input silent (useful for passwords).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat test.sh
───────┬─────────────────────────────────────────────────────────────────
       │ File: test.sh
───────┼─────────────────────────────────────────────────────────────────
   1   │ #!/bin/bash
   2   │ 
   3   │ echo "Hello, please enter the username and password"
   4   │ 
   5   │ read -p "Username: " usname
   6   │ read -sp "Password: " password
   7   │ 
   8   │ echo -e "\nHello $usname with this password: $password"
   9   │ 
  10   │ 
───────┴─────────────────────────────────────────────────────────────────
❯ ./test.sh
Hello, please enter the username and password
Username: Adri
Password: 
Hello Adri with this password: SuperSecret

If, Else, Elif

When doing bash scripts we can also use conditionals.

The if statement is easy to understand, but it has a specific syntax:

1
2
3
4
if [ <evaluation> ]
then
  <tasks to perform>
fi

If the evaluation is true, then the script will execute the tasks to perform between the then and fi.

The square brackets ([ ]) in the if statement are a reference to the test command, so we can use operators allowed by this command:

OperatorExpression True if…
!EXPRESSIONThe EXPRESSION is false.
-n STRINGSTRING length is greater than zero
-z STRINGThe length of STRING is zero (empty)
STRING1 != STRING2STRING1 is not equal to STRING2
STRING1 = STRING2STRING1 is equal to STRING2
INTEGER1 -eq INTEGER2INTEGER1 is equal to INTEGER2
INTEGER1 -ne INTEGER2INTEGER1 is not equal to INTEGER2
INTEGER1 -gt INTEGER2INTEGER1 is greater than INTEGER2
INTEGER1 -lt INTEGER2INTEGER1 is less than INTEGER2
INTEGER1 -ge INTEGER2INTEGER1 is greater than or equal to INTEGER 2
INTEGER1 -le INTEGER2INTEGER1 is less than or equal to INTEGER 2
-d FILEFILE exists and is a directory
-e FILEFILE exists
-r FILEFILE exists and has read permission
-s FILEFILE exists and it is not empty
-w FILEFILE exists and has write permission
-x FILEFILE exists and has execute permission

We can also define a set of actions to be executed whrn the statement is false using else:

1
2
3
4
5
6
if [ <test> ]
then 
  <actions to do when true>
else 
  <actions to do when false>
fi

The if and else statements only allow two code execution branches. We can add additional branches with the elif statement.

1
2
3
4
5
6
7
8
9
if [ <test> ]
then 
  <actions to do when true>
elif [ <another test> ]
then
  <actions to do when 2nd test is true>
else
  <actions to do when both tests are false>
fi

Boolean and Logical Operations

The AND (&&) operator executes a command only if the previous one succeeds, returns true or 0. The OR (||) operator executes a command only if the previous one fails, returns false or none 0. In the next example we can see a usage of them:

1
2
3
4
echo $usertest
adritest
❯ grep $usertest /etc/passwd && echo "$usertest exists!" || echo "$usertest" doesn't exist!"
adritest doesn't exist!

This opperators can also be used in a test to compare values or test results. When using them like this, AND is true when both conditions are true. However, OR is true when at least one is true.

Loops

We can create loops using for or while.

For

The syntax of a for loop looks like this:

1
2
3
4
for variable in <list>
do
  <actions to perform>
done

The loop will iterate over the list and in each iteration it will assign the item to the variable and then perform the actions defined between the do and done. It will do this until the list is exhausted. We can create a list doing another command inside the parentheses preceded by the $, reading files, using brace expansion ({1..10}), etc.:

1
2
3
4
5
6
7
8
9
10
11
12
❯ for iter in $(seq 1 5); do echo $iter; done
1
2
3
4
5
❯ for iter in {1..5}; do echo $iter; done
1
2
3
4
5

While

While loops execute some actions while the expression is true. They use the square brackets ([]) for the tests.

1
2
3
4
while [ <test> ]
do 
  <action to perform>
done

In order to do the same example we used for the for loops, we need to define a counter variable. We are going to use the double parenthesis (( )) to perform an arithmetic expansion and evaluation at the same time:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cat test.sh
───────┬─────────────────────────────────────────────────────────────────
       │ File: test.sh
───────┼─────────────────────────────────────────────────────────────────
   1   │ #!/bin/bash
   2   │ 
   3   │ counter=1
   4   │ 
   5   │ while [ $counter -le 5 ]
   6   │ do
   7   │    echo $counter
   8   │    ((counter++))
   9   │ done
  10   │ 
───────┴─────────────────────────────────────────────────────────────────
❯ ./test.sh
1
2
3
4
5

Functions

Last but not least, we can also define functions inside bash scripts. They are useful when we want to perform the same action multiple times. They may be written in two different formats:

1
2
3
4
5
6
7
function function_name {
<actions to perform>
}

function_name() {
<actions to perform>
}

As in other programming languages, functions can accept arguments, however, we don’t need to define them (we can access to them using the $1, $2… variables. Let’s create an example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat test.sh
───────┬─────────────────────────────────────────────────────────────────
       │ File: test.sh
───────┼─────────────────────────────────────────────────────────────────
   1   │ #!/bin/bash
   2   │ 
   3   │ function_with_args() {
   4   │  echo "The argument passed to this function is $1"
   5   │ }
   6   │ 
   7   │ function_with_args 1111
   8   │  
───────┴─────────────────────────────────────────────────────────────────
❯ ./test.sh
The argument passed to this function is 1111

Bash functions can also return values. They return an exit status (0 for success and non-zero for failure) and they can return an arbitary value using the $? global variable. However, we can set global variables in order to simulate a traditional return.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cat test.sh
───────┬─────────────────────────────────────────────────────────────────
       │ File: test.sh
───────┼─────────────────────────────────────────────────────────────────
   1   │ #!/bin/bash
   2   │ 
   3   │ i_love_24() {
   4   │  return 24
   5   │ }
   6   │ 
   7   │ i_love_24
   8   │ echo "Wow, the previous function returned $?, unexpected!"
───────┴─────────────────────────────────────────────────────────────────
❯ ./test.sh
Wow, the previous function returned 24, unexpected!

A global variable can be accessed everywhere in the code, however a local variable can only be seen within the function, block of code or subshell. It is possible to overlay a global variable by re-declaring it preceded with local keyword, leaving the global variable untouched:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
cat test.sh
───────┬─────────────────────────────────────────────────────────────────
       │ File: test.sh
───────┼─────────────────────────────────────────────────────────────────
   1   │ #!/bin/bash
   2   │ name="Adri"
   3   │ test() {
   4   │  local name="Local Adri"
   5   │  echo "The name inside the function is: $name"
   6   │ }
   7   │ test
   8   │ echo "The name outside the function is: $name"
───────┴─────────────────────────────────────────────────────────────────
❯ ./test.sh
The name inside the function is: Local Adri
The name outside the function is: Adri
This post is licensed under CC BY 4.0 by the author.