Bash Scripting Series — Level 2

 

Bash Scripting Series — Level 2: Directory Validation and Conditional Statements

Introduction

In Level 1, we explored the Linux command line and learned how to navigate the filesystem and execute basic commands.

In Level 2, we begin writing scripts that make decisions based on specific conditions.

Consider a script that saves security scan results to a directory. Before saving anything, it should verify that the destination exists. This helps prevent avoidable errors and makes the script more reliable.

In this lesson, we will build a directory validation script and explore the Bash concepts behind it, how to run and test it, and how to expand it into a more useful tool.

1. The Original Script

#!/bin/bash

# Ask the user for a directory path
read -p "Enter a directory path: " dir

# Check whether input was provided
if [ -z "$dir" ]; then
    echo "Error: No directory path provided."
    exit 1
fi

# Check whether the directory exists
if [ -d "$dir" ]; then
    echo "Directory exists: $dir"
    exit 0
else
    echo "Directory does not exist: $dir"
    exit 2
fi

The script follows five steps:

  1. Collect a directory path from the user.

  2. Reject empty input.

  3. Check whether the path identifies an existing directory.

  4. Display the appropriate result.

  5. Return an exit status indicating the outcome.

Let's examine the components that make this possible.

2. Understanding the Script

The Shebang

#!/bin/bash

The shebang identifies the interpreter used when the script is executed directly. Here, it specifies Bash, which provides features such as variables, conditional statements, loops, and functions.

Reading User Input

read -p "Enter a directory path: " dir

The read command accepts input, while -p displays a prompt. The variable dir stores whatever the user enters.

For example, if the user enters:

/home/kali/Desktop

The variable contains that path, which can subsequently be accessed with $dir.

This script uses interactive input, not command-line arguments. We will explore the alternative later.

Checking for Empty Input

if [ -z "$dir" ]; then
    echo "Error: No directory path provided."
    exit 1
fi

An if statement allows Bash to execute commands conditionally.

The expression [ -z "$dir" ] checks whether the variable contains an empty string. The -z operator returns true when the string has zero length.

If the user presses Enter without typing a path, the script prints an error and executes exit 1.

The exit command terminates the script and returns a status to the calling process. Because the script exits here, it never reaches the directory check.

The general structure of a Bash conditional is:

if [ condition ]; then
    # Commands when the condition is true
else
    # Commands when the condition is false
fi

The else branch is optional, and fi closes the conditional statement.

Checking Whether the Directory Exists

if [ -d "$dir" ]; then
    echo "Directory exists: $dir"
    exit 0
else
    echo "Directory does not exist: $dir"
    exit 2
fi

The -d test checks whether the path exists and refers to a directory.

This distinction matters because a path can exist without being a directory. For example, /etc/passwd is normally a regular file, so [ -d "/etc/passwd" ] evaluates to false.

When the test succeeds, the script prints a confirmation and exits with status 0. Otherwise, it prints a message and returns status 2.

Notice the quotes around "$dir". They preserve the path as a single argument, including when it contains spaces. For example, /home/kali/My Projects must be treated as one path rather than two separate words.

3. Understanding Exit Codes

An exit code communicates the result of a command or script to whatever launched it. Conventionally, 0 means success, while a non-zero value indicates a failure or another unsuccessful outcome.

Our script assigns these meanings:

Exit codeMeaning in this script
0The directory exists.
1The user supplied no path.
2The supplied path is not an existing directory.

These are meanings chosen by our script; other programs may use the same numbers differently.

Checking the Return Status

Bash stores the previous command's exit status in $?.

Run the script and then inspect its result:

./directory_check.sh
echo $?

If you enter an existing directory, the status is 0. Empty input returns 1, while a missing directory returns 2.

Run echo $? immediately after the script, because executing another command first can overwrite the status you intended to inspect.

4. Running the Script in Linux

You can follow these steps on Ubuntu, Kali Linux, or another typical Linux distribution.

Step 1: Create the File

Open a terminal and run:

nano directory_check.sh

Paste the script into Nano.

Save with Ctrl + O, press Enter, and exit using Ctrl + X.

Step 2: Make It Executable

chmod +x directory_check.sh

The chmod command changes file permissions, and +x adds execute permission.

Step 3: Run It

./directory_check.sh

Alternatively, execute it through Bash:

bash directory_check.sh

The second method does not require the script file itself to have execute permission.

Step 4: Test Different Scenarios

Use the following cases to verify the script's behavior.

Test A — Existing directory

Enter a directory that exists on your computer, such as /home/kali if that path is available.

Expected output:

Directory exists: /home/kali

Expected exit code: 0.

Test B — Missing directory

Enter a path you know does not exist:

/tmp/directory-that-does-not-exist-12345

Expected output:

Directory does not exist: /tmp/directory-that-does-not-exist-12345

Expected exit code: 2.

Test C — Empty input

Run the script and press Enter without entering anything.

Expected output:

Error: No directory path provided.

Expected exit code: 1.

Test D — Regular file

Enter:

/etc/passwd

On a typical Linux installation, this path identifies a file rather than a directory. The script should therefore report that the directory does not exist.

Testing these scenarios helps verify that both the conditions and the exit statuses work as intended.

5. Absolute and Relative Paths

The script accepts both absolute and relative paths.

An absolute path begins at the filesystem root:

/home/kali/Desktop

A relative path is interpreted from the current working directory:

Desktop

If your current directory is /home/kali, the relative path Desktop refers to /home/kali/Desktop.

Use pwd to check your current working directory:

pwd

Relative paths are convenient when working within a project directory, while absolute paths are often easier to use in automation because their intended location is explicit.

6. Where and Why Is Directory Validation Useful?

Checking prerequisites before performing an operation is a common scripting practice.

Saving scan results: A security script can verify that its output directory exists before attempting to save reports.

Managing logs: An administrative script can check its log directory before writing log files. Directory existence alone does not guarantee write permission, so further checks may be needed.

Creating backups: A backup script can verify that its source directory is available before beginning the backup process.

Automating security labs: Scripts used in CTFs, authorized testing environments, or defensive security projects can validate their working directories before processing collected information.

The purpose is the same in each case: detect problems early instead of allowing the script to proceed with invalid assumptions.

7. Expanding the Script

Once you understand the original script, you can extend it in several useful directions.

Expansion 1: Accept Command-Line Arguments

Instead of prompting for a path every time, we can let the user supply it when launching the script.

Bash provides positional parameters:

  • $0 refers to the script's invocation name or path.

  • $1 contains the first argument.

  • $2 contains the second argument.

  • $# contains the number of positional arguments.

Replace the original script with this version:

#!/bin/bash

if [ "$#" -gt 1 ]; then
    echo "Usage: $0 [directory_path]"
    exit 1
fi

if [ "$#" -eq 1 ]; then
    dir="$1"
else
    read -r -p "Enter a directory path: " dir
fi

if [ -z "$dir" ]; then
    echo "Error: No directory path provided."
    exit 1
fi

if [ -d "$dir" ]; then
    echo "Directory exists: $dir"
    exit 0
else
    echo "Directory does not exist: $dir"
    exit 2
fi

The script now supports two ways to provide input.

Supply the path directly:

./directory_check.sh /home/kali

For a path containing spaces, quote the argument:

./directory_check.sh "/home/kali/My Projects"

Or run the script without an argument to receive the interactive prompt:

./directory_check.sh

The read -r command is used for interactive input because it preserves backslashes literally rather than treating them as escape characters.

This version is more convenient for repeated use and can also be called by other scripts.

Expansion 2: Check Permissions

A directory may exist but still be unsuitable for a particular operation. Bash provides additional tests for checking file types, existence, and access permissions.

TestPurpose
-eChecks whether a path exists.
-fChecks whether a path is a regular file.
-dChecks whether a path is a directory.
-rChecks whether the path is readable by the current process.
-wChecks whether the path is writable by the current process.
-xChecks whether a path is executable; for a directory, it tests search or traversal permission.
-nChecks whether a string is non-empty.
-zChecks whether a string is empty.

The following example checks whether a directory exists and reports its access permissions.

#!/bin/bash

read -r -p "Enter a directory path: " dir

if [ -z "$dir" ]; then
    echo "Error: No directory path provided."
    exit 1
fi

if [ ! -d "$dir" ]; then
    echo "Error: Directory does not exist: $dir"
    exit 2
fi

echo "Directory exists: $dir"

if [ -r "$dir" ]; then
    echo "Readable: Yes"
else
    echo "Readable: No"
fi

if [ -w "$dir" ]; then
    echo "Writable: Yes"
else
    echo "Writable: No"
fi

if [ -x "$dir" ]; then
    echo "Searchable: Yes"
else
    echo "Searchable: No"
fi

exit 0

The ! operator reverses the test. Therefore, [ ! -d "$dir" ] is true when the supplied path does not identify an existing directory.

The script checks permissions only after confirming that the directory exists. These tests are useful indicators, but they do not guarantee that a later operation will succeed because permissions and other restrictions may change.

Expansion 3: Create a Missing Directory

Sometimes we want a script to create a directory rather than stop when it is missing.

The mkdir command creates directories, and its -p option creates missing parent directories as needed.

mkdir -p -- "$dir"

Here is a complete example:

#!/bin/bash

read -r -p "Enter a directory path: " dir

if [ -z "$dir" ]; then
    echo "Error: No directory path provided."
    exit 1
fi

if [ -d "$dir" ]; then
    echo "Directory already exists: $dir"
    exit 0
fi

if mkdir -p -- "$dir"; then
    echo "Directory created successfully: $dir"
    exit 0
else
    echo "Error: Could not create directory: $dir"
    exit 2
fi

The expression if mkdir -p -- "$dir"; then executes mkdir and checks its exit status. The script prints the corresponding message depending on whether directory creation succeeds.

The -- marks the end of command-line options, preventing a path beginning with a hyphen from being interpreted as an option.

For example, entering /tmp/bash-lab/output creates the directory and any missing parent directories. If creation fails because of permissions or another filesystem problem, the script reports the failure.

Always verify the intended destination before allowing a script to create or modify filesystem locations.

8. Common Mistakes

Missing Spaces in Conditions

Incorrect:

if [-d "$dir"]; then

Correct:

if [ -d "$dir" ]; then

The square brackets are part of a command, so the surrounding spaces are required.

Confusing Existence with Directory Type

The -e test checks whether a path exists, while -d specifically checks whether it is a directory. Choose the condition that matches the operation your script needs to perform.

Forgetting Variable Quotes

Prefer:

[ -d "$dir" ]

over:

[ -d $dir ]

Quoting preserves paths containing spaces and avoids unintended word splitting.

Misunderstanding exit

When launched normally, exit terminates the script, not the terminal from which you started it. However, sourcing a script into your current shell can cause its exit command to terminate that shell session. For these exercises, execute the file using ./directory_check.sh or bash directory_check.sh.

Relying Only on Preliminary Checks

Checking a directory's existence or permissions does not guarantee that a later operation will succeed. When running commands such as mkdir, a backup utility, or a file-writing operation, check the command's actual exit status too.

9. Practical Exercises

Complete these exercises in your Linux virtual machine.

Exercise 1 — Directory validation: Test the original script with an existing directory, a missing directory, and empty input. Record each exit code.

Exercise 2 — File validation: Modify the script to check for a regular file using -f instead of a directory using -d.

Exercise 3 — Permission checks: Extend the script to report whether the selected directory is readable, writable, and searchable.

Exercise 4 — Command-line arguments: Accept a directory path through $1, while retaining the interactive prompt when no argument is supplied.

Exercise 5 — Automatic directory creation: Create a script that makes the requested directory if necessary and handles creation errors.

Exercise 6 — Exit status handling: Write a second script that launches the directory checker and reacts to its returned status.

For the final exercise, consider how another script could distinguish successful validation from missing input or an invalid directory.

Conclusion

In this lesson, we developed a directory validation script using Bash variables, user input, conditional statements, file tests, and exit codes.

We then extended the script to accept command-line arguments, check permissions, and create missing directories. These techniques form the foundation of more reliable automation.

Next step: Practice combining conditional statements with loops and functions. These concepts will help you build more advanced scripts for system administration, log processing, backups, and security lab workflows.

Comments

Popular posts from this blog

Information disclosure in version control history

Linux AAA

Hashcat LAB